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  }