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.

297 lines
7.8 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(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, 0, 0)
  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. bodyReader := ctx.Req.Body().ReadCloser()
  143. defer bodyReader.Close()
  144. dec := json.NewDecoder(bodyReader)
  145. if err := dec.Decode(&req); err != nil {
  146. writeStatus(ctx, 400)
  147. return
  148. }
  149. lock, err := models.CreateLFSLock(&models.LFSLock{
  150. Repo: repository,
  151. Path: req.Path,
  152. Owner: ctx.User,
  153. })
  154. if err != nil {
  155. if models.IsErrLFSLockAlreadyExist(err) {
  156. ctx.JSON(409, api.LFSLockError{
  157. Lock: lock.APIFormat(),
  158. Message: "already created lock",
  159. })
  160. return
  161. }
  162. if models.IsErrLFSUnauthorizedAction(err) {
  163. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  164. ctx.JSON(401, api.LFSLockError{
  165. Message: "You must have push access to create locks : " + err.Error(),
  166. })
  167. return
  168. }
  169. ctx.JSON(500, api.LFSLockError{
  170. Message: "internal server error : " + err.Error(),
  171. })
  172. return
  173. }
  174. ctx.JSON(201, api.LFSLockResponse{Lock: lock.APIFormat()})
  175. }
  176. // VerifyLockHandler list locks for verification
  177. func VerifyLockHandler(ctx *context.Context) {
  178. if !checkIsValidRequest(ctx) {
  179. return
  180. }
  181. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  182. userName := ctx.Params("username")
  183. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  184. authorization := ctx.Req.Header.Get("Authorization")
  185. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  186. if err != nil {
  187. log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
  188. writeStatus(ctx, 404)
  189. return
  190. }
  191. repository.MustOwner()
  192. authenticated := authenticate(ctx, repository, authorization, true)
  193. if !authenticated {
  194. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  195. ctx.JSON(401, api.LFSLockError{
  196. Message: "You must have push access to verify locks",
  197. })
  198. return
  199. }
  200. //TODO handle body json cursor and limit
  201. lockList, err := models.GetLFSLockByRepoID(repository.ID, 0, 0)
  202. if err != nil {
  203. ctx.JSON(500, api.LFSLockError{
  204. Message: "unable to list locks : " + err.Error(),
  205. })
  206. return
  207. }
  208. lockOursListAPI := make([]*api.LFSLock, 0, len(lockList))
  209. lockTheirsListAPI := make([]*api.LFSLock, 0, len(lockList))
  210. for _, l := range lockList {
  211. if l.Owner.ID == ctx.User.ID {
  212. lockOursListAPI = append(lockOursListAPI, l.APIFormat())
  213. } else {
  214. lockTheirsListAPI = append(lockTheirsListAPI, l.APIFormat())
  215. }
  216. }
  217. ctx.JSON(200, api.LFSLockListVerify{
  218. Ours: lockOursListAPI,
  219. Theirs: lockTheirsListAPI,
  220. })
  221. }
  222. // UnLockHandler delete locks
  223. func UnLockHandler(ctx *context.Context) {
  224. if !checkIsValidRequest(ctx) {
  225. return
  226. }
  227. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  228. userName := ctx.Params("username")
  229. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  230. authorization := ctx.Req.Header.Get("Authorization")
  231. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  232. if err != nil {
  233. log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
  234. writeStatus(ctx, 404)
  235. return
  236. }
  237. repository.MustOwner()
  238. authenticated := authenticate(ctx, repository, authorization, true)
  239. if !authenticated {
  240. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  241. ctx.JSON(401, api.LFSLockError{
  242. Message: "You must have push access to delete locks",
  243. })
  244. return
  245. }
  246. var req api.LFSLockDeleteRequest
  247. bodyReader := ctx.Req.Body().ReadCloser()
  248. defer bodyReader.Close()
  249. dec := json.NewDecoder(bodyReader)
  250. if err := dec.Decode(&req); err != nil {
  251. writeStatus(ctx, 400)
  252. return
  253. }
  254. lock, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.User, req.Force)
  255. if err != nil {
  256. if models.IsErrLFSUnauthorizedAction(err) {
  257. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  258. ctx.JSON(401, api.LFSLockError{
  259. Message: "You must have push access to delete locks : " + err.Error(),
  260. })
  261. return
  262. }
  263. ctx.JSON(500, api.LFSLockError{
  264. Message: "unable to delete lock : " + err.Error(),
  265. })
  266. return
  267. }
  268. ctx.JSON(200, api.LFSLockResponse{Lock: lock.APIFormat()})
  269. }