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

locks.go 10 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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/convert"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/setting"
  14. api "code.gitea.io/gitea/modules/structs"
  15. )
  16. //checkIsValidRequest check if it a valid request in case of bad request it write the response to ctx.
  17. func checkIsValidRequest(ctx *context.Context) bool {
  18. if !setting.LFS.StartServer {
  19. log.Debug("Attempt to access LFS server but LFS server is disabled")
  20. writeStatus(ctx, 404)
  21. return false
  22. }
  23. if !MetaMatcher(ctx.Req) {
  24. log.Info("Attempt access LOCKs without accepting the correct media type: %s", metaMediaType)
  25. writeStatus(ctx, 400)
  26. return false
  27. }
  28. if !ctx.IsSigned {
  29. user, _, _, err := parseToken(ctx.Req.Header.Get("Authorization"))
  30. if err != nil {
  31. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  32. writeStatus(ctx, 401)
  33. return false
  34. }
  35. ctx.User = user
  36. }
  37. return true
  38. }
  39. func handleLockListOut(ctx *context.Context, repo *models.Repository, lock *models.LFSLock, err error) {
  40. if err != nil {
  41. if models.IsErrLFSLockNotExist(err) {
  42. ctx.JSON(200, api.LFSLockList{
  43. Locks: []*api.LFSLock{},
  44. })
  45. return
  46. }
  47. ctx.JSON(500, api.LFSLockError{
  48. Message: "unable to list locks : Internal Server Error",
  49. })
  50. return
  51. }
  52. if repo.ID != lock.RepoID {
  53. ctx.JSON(200, api.LFSLockList{
  54. Locks: []*api.LFSLock{},
  55. })
  56. return
  57. }
  58. ctx.JSON(200, api.LFSLockList{
  59. Locks: []*api.LFSLock{convert.ToLFSLock(lock)},
  60. })
  61. }
  62. // GetListLockHandler list locks
  63. func GetListLockHandler(ctx *context.Context) {
  64. if !checkIsValidRequest(ctx) {
  65. // Status is written in checkIsValidRequest
  66. return
  67. }
  68. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  69. rv := unpack(ctx)
  70. repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
  71. if err != nil {
  72. log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
  73. writeStatus(ctx, 404)
  74. return
  75. }
  76. repository.MustOwner()
  77. authenticated := authenticate(ctx, repository, rv.Authorization, false)
  78. if !authenticated {
  79. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  80. ctx.JSON(401, api.LFSLockError{
  81. Message: "You must have pull access to list locks",
  82. })
  83. return
  84. }
  85. cursor := ctx.QueryInt("cursor")
  86. if cursor < 0 {
  87. cursor = 0
  88. }
  89. limit := ctx.QueryInt("limit")
  90. if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
  91. limit = setting.LFS.LocksPagingNum
  92. } else if limit < 0 {
  93. limit = 0
  94. }
  95. id := ctx.Query("id")
  96. if id != "" { //Case where we request a specific id
  97. v, err := strconv.ParseInt(id, 10, 64)
  98. if err != nil {
  99. ctx.JSON(400, api.LFSLockError{
  100. Message: "bad request : " + err.Error(),
  101. })
  102. return
  103. }
  104. lock, err := models.GetLFSLockByID(v)
  105. if err != nil && !models.IsErrLFSLockNotExist(err) {
  106. log.Error("Unable to get lock with ID[%s]: Error: %v", v, err)
  107. }
  108. handleLockListOut(ctx, repository, lock, err)
  109. return
  110. }
  111. path := ctx.Query("path")
  112. if path != "" { //Case where we request a specific id
  113. lock, err := models.GetLFSLock(repository, path)
  114. if err != nil && !models.IsErrLFSLockNotExist(err) {
  115. log.Error("Unable to get lock for repository %-v with path %s: Error: %v", repository, path, err)
  116. }
  117. handleLockListOut(ctx, repository, lock, err)
  118. return
  119. }
  120. //If no query params path or id
  121. lockList, err := models.GetLFSLockByRepoID(repository.ID, cursor, limit)
  122. if err != nil {
  123. log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
  124. ctx.JSON(500, api.LFSLockError{
  125. Message: "unable to list locks : Internal Server Error",
  126. })
  127. return
  128. }
  129. lockListAPI := make([]*api.LFSLock, len(lockList))
  130. next := ""
  131. for i, l := range lockList {
  132. lockListAPI[i] = convert.ToLFSLock(l)
  133. }
  134. if limit > 0 && len(lockList) == limit {
  135. next = strconv.Itoa(cursor + 1)
  136. }
  137. ctx.JSON(200, api.LFSLockList{
  138. Locks: lockListAPI,
  139. Next: next,
  140. })
  141. }
  142. // PostLockHandler create lock
  143. func PostLockHandler(ctx *context.Context) {
  144. if !checkIsValidRequest(ctx) {
  145. // Status is written in checkIsValidRequest
  146. return
  147. }
  148. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  149. userName := ctx.Params("username")
  150. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  151. authorization := ctx.Req.Header.Get("Authorization")
  152. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  153. if err != nil {
  154. log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
  155. writeStatus(ctx, 404)
  156. return
  157. }
  158. repository.MustOwner()
  159. authenticated := authenticate(ctx, repository, authorization, true)
  160. if !authenticated {
  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",
  164. })
  165. return
  166. }
  167. var req api.LFSLockRequest
  168. bodyReader := ctx.Req.Body().ReadCloser()
  169. defer bodyReader.Close()
  170. dec := json.NewDecoder(bodyReader)
  171. if err := dec.Decode(&req); err != nil {
  172. log.Warn("Failed to decode lock request as json. Error: %v", err)
  173. writeStatus(ctx, 400)
  174. return
  175. }
  176. lock, err := models.CreateLFSLock(&models.LFSLock{
  177. Repo: repository,
  178. Path: req.Path,
  179. Owner: ctx.User,
  180. })
  181. if err != nil {
  182. if models.IsErrLFSLockAlreadyExist(err) {
  183. ctx.JSON(409, api.LFSLockError{
  184. Lock: convert.ToLFSLock(lock),
  185. Message: "already created lock",
  186. })
  187. return
  188. }
  189. if models.IsErrLFSUnauthorizedAction(err) {
  190. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  191. ctx.JSON(401, api.LFSLockError{
  192. Message: "You must have push access to create locks : " + err.Error(),
  193. })
  194. return
  195. }
  196. log.Error("Unable to CreateLFSLock in repository %-v at %s for user %-v: Error: %v", repository, req.Path, ctx.User, err)
  197. ctx.JSON(500, api.LFSLockError{
  198. Message: "internal server error : Internal Server Error",
  199. })
  200. return
  201. }
  202. ctx.JSON(201, api.LFSLockResponse{Lock: convert.ToLFSLock(lock)})
  203. }
  204. // VerifyLockHandler list locks for verification
  205. func VerifyLockHandler(ctx *context.Context) {
  206. if !checkIsValidRequest(ctx) {
  207. // Status is written in checkIsValidRequest
  208. return
  209. }
  210. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  211. userName := ctx.Params("username")
  212. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  213. authorization := ctx.Req.Header.Get("Authorization")
  214. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  215. if err != nil {
  216. log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
  217. writeStatus(ctx, 404)
  218. return
  219. }
  220. repository.MustOwner()
  221. authenticated := authenticate(ctx, repository, authorization, true)
  222. if !authenticated {
  223. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  224. ctx.JSON(401, api.LFSLockError{
  225. Message: "You must have push access to verify locks",
  226. })
  227. return
  228. }
  229. cursor := ctx.QueryInt("cursor")
  230. if cursor < 0 {
  231. cursor = 0
  232. }
  233. limit := ctx.QueryInt("limit")
  234. if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
  235. limit = setting.LFS.LocksPagingNum
  236. } else if limit < 0 {
  237. limit = 0
  238. }
  239. lockList, err := models.GetLFSLockByRepoID(repository.ID, cursor, limit)
  240. if err != nil {
  241. log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
  242. ctx.JSON(500, api.LFSLockError{
  243. Message: "unable to list locks : Internal Server Error",
  244. })
  245. return
  246. }
  247. next := ""
  248. if limit > 0 && len(lockList) == limit {
  249. next = strconv.Itoa(cursor + 1)
  250. }
  251. lockOursListAPI := make([]*api.LFSLock, 0, len(lockList))
  252. lockTheirsListAPI := make([]*api.LFSLock, 0, len(lockList))
  253. for _, l := range lockList {
  254. if l.Owner.ID == ctx.User.ID {
  255. lockOursListAPI = append(lockOursListAPI, convert.ToLFSLock(l))
  256. } else {
  257. lockTheirsListAPI = append(lockTheirsListAPI, convert.ToLFSLock(l))
  258. }
  259. }
  260. ctx.JSON(200, api.LFSLockListVerify{
  261. Ours: lockOursListAPI,
  262. Theirs: lockTheirsListAPI,
  263. Next: next,
  264. })
  265. }
  266. // UnLockHandler delete locks
  267. func UnLockHandler(ctx *context.Context) {
  268. if !checkIsValidRequest(ctx) {
  269. // Status is written in checkIsValidRequest
  270. return
  271. }
  272. ctx.Resp.Header().Set("Content-Type", metaMediaType)
  273. userName := ctx.Params("username")
  274. repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
  275. authorization := ctx.Req.Header.Get("Authorization")
  276. repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
  277. if err != nil {
  278. log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
  279. writeStatus(ctx, 404)
  280. return
  281. }
  282. repository.MustOwner()
  283. authenticated := authenticate(ctx, repository, authorization, true)
  284. if !authenticated {
  285. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  286. ctx.JSON(401, api.LFSLockError{
  287. Message: "You must have push access to delete locks",
  288. })
  289. return
  290. }
  291. var req api.LFSLockDeleteRequest
  292. bodyReader := ctx.Req.Body().ReadCloser()
  293. defer bodyReader.Close()
  294. dec := json.NewDecoder(bodyReader)
  295. if err := dec.Decode(&req); err != nil {
  296. log.Warn("Failed to decode lock request as json. Error: %v", err)
  297. writeStatus(ctx, 400)
  298. return
  299. }
  300. lock, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.User, req.Force)
  301. if err != nil {
  302. if models.IsErrLFSUnauthorizedAction(err) {
  303. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
  304. ctx.JSON(401, api.LFSLockError{
  305. Message: "You must have push access to delete locks : " + err.Error(),
  306. })
  307. return
  308. }
  309. log.Error("Unable to DeleteLFSLockByID[%d] by user %-v with force %t: Error: %v", ctx.ParamsInt64("lid"), ctx.User, req.Force, err)
  310. ctx.JSON(500, api.LFSLockError{
  311. Message: "unable to delete lock : Internal Server Error",
  312. })
  313. return
  314. }
  315. ctx.JSON(200, api.LFSLockResponse{Lock: convert.ToLFSLock(lock)})
  316. }