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.

locks.go 9.7KB

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