2017-11-28 23:58:37 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package lfs
import (
2021-04-05 18:30:52 +03:00
"net/http"
2017-11-28 23:58:37 +03:00
"strconv"
2017-11-29 02:35:23 +03:00
"strings"
2017-11-28 23:58:37 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
2020-12-03 17:05:48 +03:00
"code.gitea.io/gitea/modules/convert"
2019-05-28 13:32:41 +03:00
"code.gitea.io/gitea/modules/log"
2017-11-28 23:58:37 +03:00
"code.gitea.io/gitea/modules/setting"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2021-03-02 00:08:10 +03:00
jsoniter "github.com/json-iterator/go"
2017-11-28 23:58:37 +03:00
)
2018-01-27 19:48:15 +03:00
//checkIsValidRequest check if it a valid request in case of bad request it write the response to ctx.
2019-06-12 22:41:28 +03:00
func checkIsValidRequest ( ctx * context . Context ) bool {
2017-11-28 23:58:37 +03:00
if ! setting . LFS . StartServer {
2020-03-09 22:56:18 +03:00
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2021-04-05 18:30:52 +03:00
writeStatus ( ctx , http . StatusNotFound )
2018-01-27 19:48:15 +03:00
return false
}
if ! MetaMatcher ( ctx . Req ) {
2020-03-09 22:56:18 +03:00
log . Info ( "Attempt access LOCKs without accepting the correct media type: %s" , metaMediaType )
2021-04-05 18:30:52 +03:00
writeStatus ( ctx , http . StatusBadRequest )
2018-01-27 19:48:15 +03:00
return false
2017-11-28 23:58:37 +03:00
}
2018-01-27 19:48:15 +03:00
if ! ctx . IsSigned {
user , _ , _ , err := parseToken ( ctx . Req . Header . Get ( "Authorization" ) )
if err != nil {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
2021-04-05 18:30:52 +03:00
writeStatus ( ctx , http . StatusUnauthorized )
2018-01-27 19:48:15 +03:00
return false
}
ctx . User = user
2017-11-28 23:58:37 +03:00
}
2018-01-27 19:48:15 +03:00
return true
2017-11-28 23:58:37 +03:00
}
2019-05-28 13:32:41 +03:00
func handleLockListOut ( ctx * context . Context , repo * models . Repository , lock * models . LFSLock , err error ) {
2017-11-28 23:58:37 +03:00
if err != nil {
if models . IsErrLFSLockNotExist ( err ) {
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , api . LFSLockList {
2017-11-28 23:58:37 +03:00
Locks : [ ] * api . LFSLock { } ,
} )
return
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusInternalServerError , api . LFSLockError {
2020-03-09 22:56:18 +03:00
Message : "unable to list locks : Internal Server Error" ,
2017-11-28 23:58:37 +03:00
} )
return
}
2019-05-28 13:32:41 +03:00
if repo . ID != lock . RepoID {
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , api . LFSLockList {
2017-11-28 23:58:37 +03:00
Locks : [ ] * api . LFSLock { } ,
} )
return
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , api . LFSLockList {
2020-12-03 17:05:48 +03:00
Locks : [ ] * api . LFSLock { convert . ToLFSLock ( lock ) } ,
2017-11-28 23:58:37 +03:00
} )
}
// GetListLockHandler list locks
func GetListLockHandler ( ctx * context . Context ) {
2019-06-12 22:41:28 +03:00
if ! checkIsValidRequest ( ctx ) {
2020-03-09 22:56:18 +03:00
// Status is written in checkIsValidRequest
2017-11-28 23:58:37 +03:00
return
}
ctx . Resp . Header ( ) . Set ( "Content-Type" , metaMediaType )
2019-05-28 13:32:41 +03:00
rv := unpack ( ctx )
repository , err := models . GetRepositoryByOwnerAndName ( rv . User , rv . Repo )
2017-11-28 23:58:37 +03:00
if err != nil {
2019-05-28 13:32:41 +03:00
log . Debug ( "Could not find repository: %s/%s - %s" , rv . User , rv . Repo , err )
writeStatus ( ctx , 404 )
return
}
repository . MustOwner ( )
authenticated := authenticate ( ctx , repository , rv . Authorization , false )
if ! authenticated {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusUnauthorized , api . LFSLockError {
2019-05-28 13:32:41 +03:00
Message : "You must have pull access to list locks" ,
2017-11-28 23:58:37 +03:00
} )
return
}
2020-03-09 22:56:18 +03:00
cursor := ctx . QueryInt ( "cursor" )
if cursor < 0 {
cursor = 0
}
limit := ctx . QueryInt ( "limit" )
if limit > setting . LFS . LocksPagingNum && setting . LFS . LocksPagingNum > 0 {
limit = setting . LFS . LocksPagingNum
} else if limit < 0 {
limit = 0
}
2017-11-28 23:58:37 +03:00
id := ctx . Query ( "id" )
if id != "" { //Case where we request a specific id
v , err := strconv . ParseInt ( id , 10 , 64 )
if err != nil {
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusBadRequest , api . LFSLockError {
2017-11-28 23:58:37 +03:00
Message : "bad request : " + err . Error ( ) ,
} )
return
}
2019-07-23 21:50:39 +03:00
lock , err := models . GetLFSLockByID ( v )
2020-03-09 22:56:18 +03:00
if err != nil && ! models . IsErrLFSLockNotExist ( err ) {
log . Error ( "Unable to get lock with ID[%s]: Error: %v" , v , err )
}
2019-05-28 13:32:41 +03:00
handleLockListOut ( ctx , repository , lock , err )
2017-11-28 23:58:37 +03:00
return
}
path := ctx . Query ( "path" )
if path != "" { //Case where we request a specific id
2019-05-28 13:32:41 +03:00
lock , err := models . GetLFSLock ( repository , path )
2020-03-09 22:56:18 +03:00
if err != nil && ! models . IsErrLFSLockNotExist ( err ) {
log . Error ( "Unable to get lock for repository %-v with path %s: Error: %v" , repository , path , err )
}
2019-05-28 13:32:41 +03:00
handleLockListOut ( ctx , repository , lock , err )
2017-11-28 23:58:37 +03:00
return
}
//If no query params path or id
2020-03-09 22:56:18 +03:00
lockList , err := models . GetLFSLockByRepoID ( repository . ID , cursor , limit )
2017-11-28 23:58:37 +03:00
if err != nil {
2020-03-09 22:56:18 +03:00
log . Error ( "Unable to list locks for repository ID[%d]: Error: %v" , repository . ID , err )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusInternalServerError , api . LFSLockError {
2020-03-09 22:56:18 +03:00
Message : "unable to list locks : Internal Server Error" ,
2017-11-28 23:58:37 +03:00
} )
return
}
lockListAPI := make ( [ ] * api . LFSLock , len ( lockList ) )
2020-03-09 22:56:18 +03:00
next := ""
2017-11-28 23:58:37 +03:00
for i , l := range lockList {
2020-12-03 17:05:48 +03:00
lockListAPI [ i ] = convert . ToLFSLock ( l )
2017-11-28 23:58:37 +03:00
}
2020-03-09 22:56:18 +03:00
if limit > 0 && len ( lockList ) == limit {
next = strconv . Itoa ( cursor + 1 )
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , api . LFSLockList {
2017-11-28 23:58:37 +03:00
Locks : lockListAPI ,
2020-03-09 22:56:18 +03:00
Next : next ,
2017-11-28 23:58:37 +03:00
} )
}
// PostLockHandler create lock
func PostLockHandler ( ctx * context . Context ) {
2019-06-12 22:41:28 +03:00
if ! checkIsValidRequest ( ctx ) {
2020-03-09 22:56:18 +03:00
// Status is written in checkIsValidRequest
2017-11-28 23:58:37 +03:00
return
}
ctx . Resp . Header ( ) . Set ( "Content-Type" , metaMediaType )
2019-05-28 13:32:41 +03:00
userName := ctx . Params ( "username" )
repoName := strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" )
authorization := ctx . Req . Header . Get ( "Authorization" )
repository , err := models . GetRepositoryByOwnerAndName ( userName , repoName )
if err != nil {
2020-03-09 22:56:18 +03:00
log . Error ( "Unable to get repository: %s/%s Error: %v" , userName , repoName , err )
2019-05-28 13:32:41 +03:00
writeStatus ( ctx , 404 )
return
}
repository . MustOwner ( )
authenticated := authenticate ( ctx , repository , authorization , true )
if ! authenticated {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusUnauthorized , api . LFSLockError {
2019-05-28 13:32:41 +03:00
Message : "You must have push access to create locks" ,
} )
return
}
2017-11-28 23:58:37 +03:00
var req api . LFSLockRequest
2021-01-26 18:36:53 +03:00
bodyReader := ctx . Req . Body
2019-10-10 20:42:28 +03:00
defer bodyReader . Close ( )
2021-03-02 00:08:10 +03:00
json := jsoniter . ConfigCompatibleWithStandardLibrary
2019-10-10 20:42:28 +03:00
dec := json . NewDecoder ( bodyReader )
2019-05-28 13:32:41 +03:00
if err := dec . Decode ( & req ) ; err != nil {
2020-03-09 22:56:18 +03:00
log . Warn ( "Failed to decode lock request as json. Error: %v" , err )
2017-11-28 23:58:37 +03:00
writeStatus ( ctx , 400 )
return
}
lock , err := models . CreateLFSLock ( & models . LFSLock {
2019-05-28 13:32:41 +03:00
Repo : repository ,
2018-01-27 19:48:15 +03:00
Path : req . Path ,
Owner : ctx . User ,
2017-11-28 23:58:37 +03:00
} )
if err != nil {
if models . IsErrLFSLockAlreadyExist ( err ) {
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusConflict , api . LFSLockError {
2020-12-03 17:05:48 +03:00
Lock : convert . ToLFSLock ( lock ) ,
2017-11-28 23:58:37 +03:00
Message : "already created lock" ,
} )
return
}
2018-01-27 19:48:15 +03:00
if models . IsErrLFSUnauthorizedAction ( err ) {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusUnauthorized , api . LFSLockError {
2017-11-28 23:58:37 +03:00
Message : "You must have push access to create locks : " + err . Error ( ) ,
} )
return
}
2020-03-09 22:56:18 +03:00
log . Error ( "Unable to CreateLFSLock in repository %-v at %s for user %-v: Error: %v" , repository , req . Path , ctx . User , err )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusInternalServerError , api . LFSLockError {
2020-03-09 22:56:18 +03:00
Message : "internal server error : Internal Server Error" ,
2017-11-28 23:58:37 +03:00
} )
return
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusCreated , api . LFSLockResponse { Lock : convert . ToLFSLock ( lock ) } )
2017-11-28 23:58:37 +03:00
}
// VerifyLockHandler list locks for verification
func VerifyLockHandler ( ctx * context . Context ) {
2019-06-12 22:41:28 +03:00
if ! checkIsValidRequest ( ctx ) {
2020-03-09 22:56:18 +03:00
// Status is written in checkIsValidRequest
2017-11-28 23:58:37 +03:00
return
}
ctx . Resp . Header ( ) . Set ( "Content-Type" , metaMediaType )
2019-05-28 13:32:41 +03:00
userName := ctx . Params ( "username" )
repoName := strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" )
authorization := ctx . Req . Header . Get ( "Authorization" )
repository , err := models . GetRepositoryByOwnerAndName ( userName , repoName )
2017-11-28 23:58:37 +03:00
if err != nil {
2020-03-09 22:56:18 +03:00
log . Error ( "Unable to get repository: %s/%s Error: %v" , userName , repoName , err )
2019-05-28 13:32:41 +03:00
writeStatus ( ctx , 404 )
return
}
repository . MustOwner ( )
authenticated := authenticate ( ctx , repository , authorization , true )
if ! authenticated {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusUnauthorized , api . LFSLockError {
2019-05-28 13:32:41 +03:00
Message : "You must have push access to verify locks" ,
2017-11-28 23:58:37 +03:00
} )
return
}
2020-03-09 22:56:18 +03:00
cursor := ctx . QueryInt ( "cursor" )
if cursor < 0 {
cursor = 0
}
limit := ctx . QueryInt ( "limit" )
if limit > setting . LFS . LocksPagingNum && setting . LFS . LocksPagingNum > 0 {
limit = setting . LFS . LocksPagingNum
} else if limit < 0 {
limit = 0
}
lockList , err := models . GetLFSLockByRepoID ( repository . ID , cursor , limit )
2017-11-28 23:58:37 +03:00
if err != nil {
2020-03-09 22:56:18 +03:00
log . Error ( "Unable to list locks for repository ID[%d]: Error: %v" , repository . ID , err )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusInternalServerError , api . LFSLockError {
2020-03-09 22:56:18 +03:00
Message : "unable to list locks : Internal Server Error" ,
2017-11-28 23:58:37 +03:00
} )
return
}
2020-03-09 22:56:18 +03:00
next := ""
if limit > 0 && len ( lockList ) == limit {
next = strconv . Itoa ( cursor + 1 )
}
2017-11-28 23:58:37 +03:00
lockOursListAPI := make ( [ ] * api . LFSLock , 0 , len ( lockList ) )
lockTheirsListAPI := make ( [ ] * api . LFSLock , 0 , len ( lockList ) )
for _ , l := range lockList {
if l . Owner . ID == ctx . User . ID {
2020-12-03 17:05:48 +03:00
lockOursListAPI = append ( lockOursListAPI , convert . ToLFSLock ( l ) )
2017-11-28 23:58:37 +03:00
} else {
2020-12-03 17:05:48 +03:00
lockTheirsListAPI = append ( lockTheirsListAPI , convert . ToLFSLock ( l ) )
2017-11-28 23:58:37 +03:00
}
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , api . LFSLockListVerify {
2017-11-28 23:58:37 +03:00
Ours : lockOursListAPI ,
Theirs : lockTheirsListAPI ,
2020-03-09 22:56:18 +03:00
Next : next ,
2017-11-28 23:58:37 +03:00
} )
}
// UnLockHandler delete locks
func UnLockHandler ( ctx * context . Context ) {
2019-06-12 22:41:28 +03:00
if ! checkIsValidRequest ( ctx ) {
2020-03-09 22:56:18 +03:00
// Status is written in checkIsValidRequest
2017-11-28 23:58:37 +03:00
return
}
ctx . Resp . Header ( ) . Set ( "Content-Type" , metaMediaType )
2019-05-28 13:32:41 +03:00
userName := ctx . Params ( "username" )
repoName := strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" )
authorization := ctx . Req . Header . Get ( "Authorization" )
repository , err := models . GetRepositoryByOwnerAndName ( userName , repoName )
if err != nil {
2020-03-09 22:56:18 +03:00
log . Error ( "Unable to get repository: %s/%s Error: %v" , userName , repoName , err )
2019-05-28 13:32:41 +03:00
writeStatus ( ctx , 404 )
return
}
repository . MustOwner ( )
authenticated := authenticate ( ctx , repository , authorization , true )
if ! authenticated {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusUnauthorized , api . LFSLockError {
2019-05-28 13:32:41 +03:00
Message : "You must have push access to delete locks" ,
} )
return
}
2017-11-28 23:58:37 +03:00
var req api . LFSLockDeleteRequest
2021-01-26 18:36:53 +03:00
bodyReader := ctx . Req . Body
2019-10-10 20:42:28 +03:00
defer bodyReader . Close ( )
2021-03-02 00:08:10 +03:00
json := jsoniter . ConfigCompatibleWithStandardLibrary
2019-10-10 20:42:28 +03:00
dec := json . NewDecoder ( bodyReader )
2019-05-28 13:32:41 +03:00
if err := dec . Decode ( & req ) ; err != nil {
2020-03-09 22:56:18 +03:00
log . Warn ( "Failed to decode lock request as json. Error: %v" , err )
2017-11-28 23:58:37 +03:00
writeStatus ( ctx , 400 )
return
}
lock , err := models . DeleteLFSLockByID ( ctx . ParamsInt64 ( "lid" ) , ctx . User , req . Force )
if err != nil {
2018-01-27 19:48:15 +03:00
if models . IsErrLFSUnauthorizedAction ( err ) {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusUnauthorized , api . LFSLockError {
2017-11-28 23:58:37 +03:00
Message : "You must have push access to delete locks : " + err . Error ( ) ,
} )
return
}
2020-03-09 22:56:18 +03:00
log . Error ( "Unable to DeleteLFSLockByID[%d] by user %-v with force %t: Error: %v" , ctx . ParamsInt64 ( "lid" ) , ctx . User , req . Force , err )
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusInternalServerError , api . LFSLockError {
2020-03-09 22:56:18 +03:00
Message : "unable to delete lock : Internal Server Error" ,
2017-11-28 23:58:37 +03:00
} )
return
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , api . LFSLockResponse { Lock : convert . ToLFSLock ( lock ) } )
2017-11-28 23:58:37 +03:00
}