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