2020-04-05 06:20:50 +00:00
// Copyright 2020 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.
2016-12-26 01:16:37 +00:00
package lfs
import (
"encoding/base64"
"fmt"
"io"
"net/http"
2017-11-08 13:04:19 +00:00
"path"
2016-12-26 01:16:37 +00:00
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
2021-04-08 22:25:57 +00:00
lfs_module "code.gitea.io/gitea/modules/lfs"
2016-12-26 01:16:37 +00:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2017-11-08 13:04:19 +00:00
2016-12-26 01:16:37 +00:00
"github.com/dgrijalva/jwt-go"
2021-03-01 21:08:10 +00:00
jsoniter "github.com/json-iterator/go"
2016-12-26 01:16:37 +00:00
)
2021-04-08 22:25:57 +00:00
// requestContext contain variables from the HTTP request.
type requestContext struct {
2016-12-26 01:16:37 +00:00
User string
Repo string
Authorization string
}
2020-03-09 19:56:18 +00:00
// Claims is a JWT Token Claims
type Claims struct {
RepoID int64
Op string
UserID int64
jwt . StandardClaims
}
2016-12-26 01:16:37 +00:00
// ObjectLink builds a URL linking to the object.
2021-04-08 22:25:57 +00:00
func ( rc * requestContext ) ObjectLink ( oid string ) string {
return setting . AppURL + path . Join ( rc . User , rc . Repo + ".git" , "info/lfs/objects" , oid )
2017-11-08 13:04:19 +00:00
}
// VerifyLink builds a URL for verifying the object.
2021-04-08 22:25:57 +00:00
func ( rc * requestContext ) VerifyLink ( ) string {
return setting . AppURL + path . Join ( rc . User , rc . Repo + ".git" , "info/lfs/verify" )
2016-12-26 01:16:37 +00:00
}
2018-05-01 01:46:04 +00:00
var oidRegExp = regexp . MustCompile ( ` ^[A-Fa-f0-9]+$ ` )
2018-07-19 15:39:19 +00:00
func isOidValid ( oid string ) bool {
return oidRegExp . MatchString ( oid )
}
2016-12-26 01:16:37 +00:00
// ObjectOidHandler is the main request routing entry point into LFS server functions
func ObjectOidHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-09 19:56:18 +00:00
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
return
}
if ctx . Req . Method == "GET" || ctx . Req . Method == "HEAD" {
if MetaMatcher ( ctx . Req ) {
2017-10-30 12:11:56 +00:00
getMetaHandler ( ctx )
2016-12-26 01:16:37 +00:00
return
}
2019-05-24 21:21:00 +00:00
getContentHandler ( ctx )
return
} else if ctx . Req . Method == "PUT" {
2016-12-26 01:16:37 +00:00
PutHandler ( ctx )
return
}
2020-03-09 19:56:18 +00:00
log . Warn ( "Unhandled LFS method: %s for %s/%s OID[%s]" , ctx . Req . Method , ctx . Params ( "username" ) , ctx . Params ( "reponame" ) , ctx . Params ( "oid" ) )
writeStatus ( ctx , 404 )
2016-12-26 01:16:37 +00:00
}
2021-04-08 22:25:57 +00:00
func getAuthenticatedRepoAndMeta ( ctx * context . Context , rc * requestContext , p lfs_module . Pointer , requireWrite bool ) ( * models . LFSMetaObject , * models . Repository ) {
if ! isOidValid ( p . Oid ) {
log . Info ( "Attempt to access invalid LFS OID[%s] in %s/%s" , p . Oid , rc . User , rc . Repo )
2018-07-19 15:39:19 +00:00
writeStatus ( ctx , 404 )
return nil , nil
}
2021-04-08 22:25:57 +00:00
repository , err := models . GetRepositoryByOwnerAndName ( rc . User , rc . Repo )
2016-12-26 01:16:37 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to get repository: %s/%s Error: %v" , rc . User , rc . Repo , err )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
2017-10-30 12:11:56 +00:00
return nil , nil
2016-12-26 01:16:37 +00:00
}
2021-04-08 22:25:57 +00:00
if ! authenticate ( ctx , repository , rc . Authorization , requireWrite ) {
2017-10-30 12:11:56 +00:00
requireAuth ( ctx )
return nil , nil
}
2016-12-26 01:16:37 +00:00
2021-04-08 22:25:57 +00:00
meta , err := repository . GetLFSMetaObjectByOid ( p . Oid )
2016-12-26 01:16:37 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to get LFS OID[%s] Error: %v" , p . Oid , err )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
2017-10-30 12:11:56 +00:00
return nil , nil
2016-12-26 01:16:37 +00:00
}
2017-10-30 12:11:56 +00:00
return meta , repository
}
// getContentHandler gets the content from the content store
func getContentHandler ( ctx * context . Context ) {
2021-04-08 22:25:57 +00:00
rc , p := unpack ( ctx )
2017-10-30 12:11:56 +00:00
2021-04-08 22:25:57 +00:00
meta , _ := getAuthenticatedRepoAndMeta ( ctx , rc , p , false )
2017-10-30 12:11:56 +00:00
if meta == nil {
2020-03-09 19:56:18 +00:00
// Status already written in getAuthenticatedRepoAndMeta
2016-12-26 01:16:37 +00:00
return
}
// Support resume download using Range header
2020-05-11 08:37:59 +00:00
var fromByte , toByte int64
toByte = meta . Size - 1
2016-12-26 01:16:37 +00:00
statusCode := 200
if rangeHdr := ctx . Req . Header . Get ( "Range" ) ; rangeHdr != "" {
2020-05-11 08:37:59 +00:00
regex := regexp . MustCompile ( ` bytes=(\d+)\-(\d*).* ` )
2016-12-26 01:16:37 +00:00
match := regex . FindStringSubmatch ( rangeHdr )
2019-06-12 19:41:28 +00:00
if len ( match ) > 1 {
2016-12-26 01:16:37 +00:00
statusCode = 206
fromByte , _ = strconv . ParseInt ( match [ 1 ] , 10 , 32 )
2020-05-11 08:37:59 +00:00
2021-04-06 13:22:34 +00:00
if fromByte >= meta . Size {
writeStatus ( ctx , http . StatusRequestedRangeNotSatisfiable )
return
}
2020-05-11 08:37:59 +00:00
if match [ 2 ] != "" {
_toByte , _ := strconv . ParseInt ( match [ 2 ] , 10 , 32 )
if _toByte >= fromByte && _toByte < toByte {
toByte = _toByte
}
}
ctx . Resp . Header ( ) . Set ( "Content-Range" , fmt . Sprintf ( "bytes %d-%d/%d" , fromByte , toByte , meta . Size - fromByte ) )
2020-08-13 17:18:18 +00:00
ctx . Resp . Header ( ) . Set ( "Access-Control-Expose-Headers" , "Content-Range" )
2016-12-26 01:16:37 +00:00
}
}
2021-04-08 22:25:57 +00:00
contentStore := lfs_module . NewContentStore ( )
content , err := contentStore . Get ( meta . Pointer )
2016-12-26 01:16:37 +00:00
if err != nil {
2021-04-06 13:22:34 +00:00
// Errors are logged in contentStore.Get
writeStatus ( ctx , http . StatusNotFound )
2016-12-26 01:16:37 +00:00
return
}
2020-03-09 19:56:18 +00:00
defer content . Close ( )
2016-12-26 01:16:37 +00:00
2021-04-06 13:22:34 +00:00
if fromByte > 0 {
_ , err = content . Seek ( fromByte , io . SeekStart )
if err != nil {
log . Error ( "Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v" , meta . Oid , fromByte , err )
writeStatus ( ctx , http . StatusInternalServerError )
return
}
}
2020-05-11 08:37:59 +00:00
contentLength := toByte + 1 - fromByte
ctx . Resp . Header ( ) . Set ( "Content-Length" , strconv . FormatInt ( contentLength , 10 ) )
2016-12-26 01:16:37 +00:00
ctx . Resp . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
filename := ctx . Params ( "filename" )
if len ( filename ) > 0 {
decodedFilename , err := base64 . RawURLEncoding . DecodeString ( filename )
if err == nil {
ctx . Resp . Header ( ) . Set ( "Content-Disposition" , "attachment; filename=\"" + string ( decodedFilename ) + "\"" )
2020-08-13 17:18:18 +00:00
ctx . Resp . Header ( ) . Set ( "Access-Control-Expose-Headers" , "Content-Disposition" )
2016-12-26 01:16:37 +00:00
}
}
ctx . Resp . WriteHeader ( statusCode )
2020-05-11 08:37:59 +00:00
if written , err := io . CopyN ( ctx . Resp , content , contentLength ) ; err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Error whilst copying LFS OID[%s] to the response after %d bytes. Error: %v" , meta . Oid , written , err )
}
2016-12-26 01:16:37 +00:00
logRequest ( ctx . Req , statusCode )
}
2017-10-30 12:11:56 +00:00
// getMetaHandler retrieves metadata about the object
func getMetaHandler ( ctx * context . Context ) {
2021-04-08 22:25:57 +00:00
rc , p := unpack ( ctx )
2016-12-26 01:16:37 +00:00
2021-04-08 22:25:57 +00:00
meta , _ := getAuthenticatedRepoAndMeta ( ctx , rc , p , false )
2017-10-30 12:11:56 +00:00
if meta == nil {
2020-03-09 19:56:18 +00:00
// Status already written in getAuthenticatedRepoAndMeta
2016-12-26 01:16:37 +00:00
return
}
2021-04-08 22:25:57 +00:00
ctx . Resp . Header ( ) . Set ( "Content-Type" , lfs_module . MediaType )
2016-12-26 01:16:37 +00:00
if ctx . Req . Method == "GET" {
2021-03-01 21:08:10 +00:00
json := jsoniter . ConfigCompatibleWithStandardLibrary
2016-12-26 01:16:37 +00:00
enc := json . NewEncoder ( ctx . Resp )
2021-04-08 22:25:57 +00:00
if err := enc . Encode ( represent ( rc , meta . Pointer , true , false ) ) ; err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Failed to encode representation as json. Error: %v" , err )
}
2016-12-26 01:16:37 +00:00
}
logRequest ( ctx . Req , 200 )
}
// PostHandler instructs the client how to upload data
func PostHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-09 19:56:18 +00:00
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
return
}
if ! MetaMatcher ( ctx . Req ) {
2021-04-08 22:25:57 +00:00
log . Info ( "Attempt to POST without accepting the correct media type: %s" , lfs_module . MediaType )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 400 )
return
}
2021-04-08 22:25:57 +00:00
rc , p := unpack ( ctx )
2016-12-26 01:16:37 +00:00
2021-04-08 22:25:57 +00:00
repository , err := models . GetRepositoryByOwnerAndName ( rc . User , rc . Repo )
2016-12-26 01:16:37 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to get repository: %s/%s Error: %v" , rc . User , rc . Repo , err )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
return
}
2021-04-08 22:25:57 +00:00
if ! authenticate ( ctx , repository , rc . Authorization , true ) {
2016-12-26 01:16:37 +00:00
requireAuth ( ctx )
2018-05-01 01:46:04 +00:00
return
}
2021-04-08 22:25:57 +00:00
if ! isOidValid ( p . Oid ) {
log . Info ( "Invalid LFS OID[%s] attempt to POST in %s/%s" , p . Oid , rc . User , rc . Repo )
2018-05-01 01:46:04 +00:00
writeStatus ( ctx , 404 )
return
2016-12-26 01:16:37 +00:00
}
2021-04-08 22:25:57 +00:00
if setting . LFS . MaxFileSize > 0 && p . Size > setting . LFS . MaxFileSize {
log . Info ( "Denied LFS OID[%s] upload of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d" , p . Oid , p . Size , rc . User , rc . Repo , setting . LFS . MaxFileSize )
2020-02-28 04:46:57 +00:00
writeStatus ( ctx , 413 )
return
}
2021-04-08 22:25:57 +00:00
meta , err := models . NewLFSMetaObject ( & models . LFSMetaObject { Pointer : p , RepositoryID : repository . ID } )
2016-12-26 01:16:37 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to write LFS OID[%s] size %d meta object in %v/%v to database. Error: %v" , p . Oid , p . Size , rc . User , rc . Repo , err )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
return
}
2021-04-08 22:25:57 +00:00
ctx . Resp . Header ( ) . Set ( "Content-Type" , lfs_module . MediaType )
2016-12-26 01:16:37 +00:00
sentStatus := 202
2021-04-08 22:25:57 +00:00
contentStore := lfs_module . NewContentStore ( )
exist , err := contentStore . Exists ( p )
2020-09-08 15:45:10 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to check if LFS OID[%s] exist on %s / %s. Error: %v" , p . Oid , rc . User , rc . Repo , err )
2020-09-08 15:45:10 +00:00
writeStatus ( ctx , 500 )
return
}
if meta . Existing && exist {
2016-12-26 01:16:37 +00:00
sentStatus = 200
}
ctx . Resp . WriteHeader ( sentStatus )
2021-03-01 21:08:10 +00:00
json := jsoniter . ConfigCompatibleWithStandardLibrary
2016-12-26 01:16:37 +00:00
enc := json . NewEncoder ( ctx . Resp )
2021-04-08 22:25:57 +00:00
if err := enc . Encode ( represent ( rc , meta . Pointer , meta . Existing , true ) ) ; err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Failed to encode representation as json. Error: %v" , err )
}
2016-12-26 01:16:37 +00:00
logRequest ( ctx . Req , sentStatus )
}
// BatchHandler provides the batch api
func BatchHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-09 19:56:18 +00:00
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
return
}
if ! MetaMatcher ( ctx . Req ) {
2021-04-08 22:25:57 +00:00
log . Info ( "Attempt to BATCH without accepting the correct media type: %s" , lfs_module . MediaType )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 400 )
return
}
bv := unpackbatch ( ctx )
2021-04-08 22:25:57 +00:00
reqCtx := & requestContext {
User : ctx . Params ( "username" ) ,
Repo : strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" ) ,
Authorization : ctx . Req . Header . Get ( "Authorization" ) ,
}
var responseObjects [ ] * lfs_module . ObjectResponse
2016-12-26 01:16:37 +00:00
// Create a response object
for _ , object := range bv . Objects {
2018-07-19 15:39:19 +00:00
if ! isOidValid ( object . Oid ) {
2021-04-08 22:25:57 +00:00
log . Info ( "Invalid LFS OID[%s] attempt to BATCH in %s/%s" , object . Oid , reqCtx . User , reqCtx . Repo )
2018-07-19 15:39:19 +00:00
continue
}
2021-04-08 22:25:57 +00:00
repository , err := models . GetRepositoryByOwnerAndName ( reqCtx . User , reqCtx . Repo )
2016-12-26 01:16:37 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to get repository: %s/%s Error: %v" , reqCtx . User , reqCtx . Repo , err )
2016-12-26 01:16:37 +00:00
writeStatus ( ctx , 404 )
return
}
requireWrite := false
if bv . Operation == "upload" {
requireWrite = true
}
2021-04-08 22:25:57 +00:00
if ! authenticate ( ctx , repository , reqCtx . Authorization , requireWrite ) {
2016-12-26 01:16:37 +00:00
requireAuth ( ctx )
return
}
2021-04-08 22:25:57 +00:00
contentStore := lfs_module . NewContentStore ( )
2017-10-30 12:11:56 +00:00
meta , err := repository . GetLFSMetaObjectByOid ( object . Oid )
2020-09-08 15:45:10 +00:00
if err == nil { // Object is found and exists
2021-04-08 22:25:57 +00:00
exist , err := contentStore . Exists ( meta . Pointer )
2020-09-08 15:45:10 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to check if LFS OID[%s] exist on %s / %s. Error: %v" , object . Oid , reqCtx . User , reqCtx . Repo , err )
2020-09-08 15:45:10 +00:00
writeStatus ( ctx , 500 )
return
}
if exist {
2021-04-08 22:25:57 +00:00
responseObjects = append ( responseObjects , represent ( reqCtx , meta . Pointer , true , false ) )
2020-09-08 15:45:10 +00:00
continue
}
2016-12-26 01:16:37 +00:00
}
2020-03-03 20:57:27 +00:00
if requireWrite && setting . LFS . MaxFileSize > 0 && object . Size > setting . LFS . MaxFileSize {
2021-04-08 22:25:57 +00:00
log . Info ( "Denied LFS OID[%s] upload of size %d to %s/%s because of LFS_MAX_FILE_SIZE=%d" , object . Oid , object . Size , reqCtx . User , reqCtx . Repo , setting . LFS . MaxFileSize )
2020-03-03 20:57:27 +00:00
writeStatus ( ctx , 413 )
return
}
2018-07-19 15:39:19 +00:00
// Object is not found
2021-04-08 22:25:57 +00:00
meta , err = models . NewLFSMetaObject ( & models . LFSMetaObject { Pointer : object , RepositoryID : repository . ID } )
2018-07-19 15:39:19 +00:00
if err == nil {
2021-04-08 22:25:57 +00:00
exist , err := contentStore . Exists ( meta . Pointer )
2020-09-08 15:45:10 +00:00
if err != nil {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to check if LFS OID[%s] exist on %s / %s. Error: %v" , object . Oid , reqCtx . User , reqCtx . Repo , err )
2020-09-08 15:45:10 +00:00
writeStatus ( ctx , 500 )
return
}
2021-04-08 22:25:57 +00:00
responseObjects = append ( responseObjects , represent ( reqCtx , meta . Pointer , meta . Existing , ! exist ) )
2020-03-09 19:56:18 +00:00
} else {
2021-04-08 22:25:57 +00:00
log . Error ( "Unable to write LFS OID[%s] size %d meta object in %v/%v to database. Error: %v" , object . Oid , object . Size , reqCtx . User , reqCtx . Repo , err )
2016-12-26 01:16:37 +00:00
}
}
2021-04-08 22:25:57 +00:00
ctx . Resp . Header ( ) . Set ( "Content-Type" , lfs_module . MediaType )
2016-12-26 01:16:37 +00:00
2021-04-08 22:25:57 +00:00
respobj := & lfs_module . BatchResponse { Objects : responseObjects }
2016-12-26 01:16:37 +00:00
2021-03-01 21:08:10 +00:00
json := jsoniter . ConfigCompatibleWithStandardLibrary
2016-12-26 01:16:37 +00:00
enc := json . NewEncoder ( ctx . Resp )
2020-03-09 19:56:18 +00:00
if err := enc . Encode ( respobj ) ; err != nil {
log . Error ( "Failed to encode representation as json. Error: %v" , err )
}
2016-12-26 01:16:37 +00:00
logRequest ( ctx . Req , 200 )
}
// PutHandler receives data from the client and puts it into the content store
func PutHandler ( ctx * context . Context ) {
2021-04-08 22:25:57 +00:00
rc , p := unpack ( ctx )
2016-12-26 01:16:37 +00:00
2021-04-08 22:25:57 +00:00
meta , repository := getAuthenticatedRepoAndMeta ( ctx , rc , p , true )
2017-10-30 12:11:56 +00:00
if meta == nil {
2020-03-09 19:56:18 +00:00
// Status already written in getAuthenticatedRepoAndMeta
2016-12-26 01:16:37 +00:00
return
}
2021-04-08 22:25:57 +00:00
contentStore := lfs_module . NewContentStore ( )
2021-01-26 15:36:53 +00:00
defer ctx . Req . Body . Close ( )
2021-04-08 22:25:57 +00:00
if err := contentStore . Put ( meta . Pointer , ctx . Req . Body ) ; err != nil {
2020-03-09 19:56:18 +00:00
// Put will log the error itself
2016-12-26 01:16:37 +00:00
ctx . Resp . WriteHeader ( 500 )
2021-04-08 22:25:57 +00:00
if err == lfs_module . ErrSizeMismatch || err == lfs_module . ErrHashMismatch {
2020-03-09 19:56:18 +00:00
fmt . Fprintf ( ctx . Resp , ` { "message":"%s"} ` , err )
} else {
fmt . Fprintf ( ctx . Resp , ` { "message":"Internal Server Error"} ` )
}
2021-04-08 22:25:57 +00:00
if _ , err = repository . RemoveLFSMetaObjectByOid ( p . Oid ) ; err != nil {
log . Error ( "Whilst removing metaobject for LFS OID[%s] due to preceding error there was another Error: %v" , p . Oid , err )
2017-10-30 12:11:56 +00:00
}
2016-12-26 01:16:37 +00:00
return
}
logRequest ( ctx . Req , 200 )
}
2017-11-08 13:04:19 +00:00
// VerifyHandler verify oid and its size from the content store
func VerifyHandler ( ctx * context . Context ) {
if ! setting . LFS . StartServer {
2020-03-09 19:56:18 +00:00
log . Debug ( "Attempt to access LFS server but LFS server is disabled" )
2017-11-08 13:04:19 +00:00
writeStatus ( ctx , 404 )
return
}
2019-05-24 21:21:00 +00:00
if ! MetaMatcher ( ctx . Req ) {
2021-04-08 22:25:57 +00:00
log . Info ( "Attempt to VERIFY without accepting the correct media type: %s" , lfs_module . MediaType )
2017-11-08 13:04:19 +00:00
writeStatus ( ctx , 400 )
return
}
2021-04-08 22:25:57 +00:00
rc , p := unpack ( ctx )
2017-11-08 13:04:19 +00:00
2021-04-08 22:25:57 +00:00
meta , _ := getAuthenticatedRepoAndMeta ( ctx , rc , p , true )
2017-11-08 13:04:19 +00:00
if meta == nil {
2020-03-09 19:56:18 +00:00
// Status already written in getAuthenticatedRepoAndMeta
2017-11-08 13:04:19 +00:00
return
}
2021-04-08 22:25:57 +00:00
contentStore := lfs_module . NewContentStore ( )
ok , err := contentStore . Verify ( meta . Pointer )
2017-11-08 13:04:19 +00:00
if err != nil {
2020-03-09 19:56:18 +00:00
// Error will be logged in Verify
2017-11-08 13:04:19 +00:00
ctx . Resp . WriteHeader ( 500 )
2020-03-09 19:56:18 +00:00
fmt . Fprintf ( ctx . Resp , ` { "message":"Internal Server Error"} ` )
2017-11-08 13:04:19 +00:00
return
}
if ! ok {
writeStatus ( ctx , 422 )
return
}
logRequest ( ctx . Req , 200 )
}
2021-04-08 22:25:57 +00:00
// represent takes a requestContext and Meta and turns it into a ObjectResponse suitable
2016-12-26 01:16:37 +00:00
// for json encoding
2021-04-08 22:25:57 +00:00
func represent ( rc * requestContext , pointer lfs_module . Pointer , download , upload bool ) * lfs_module . ObjectResponse {
rep := & lfs_module . ObjectResponse {
Pointer : pointer ,
Actions : make ( map [ string ] * lfs_module . Link ) ,
2016-12-26 01:16:37 +00:00
}
header := make ( map [ string ] string )
2021-04-08 22:25:57 +00:00
if rc . Authorization == "" {
2016-12-26 01:16:37 +00:00
//https://github.com/github/git-lfs/issues/1088
header [ "Authorization" ] = "Authorization: Basic dummy"
} else {
2021-04-08 22:25:57 +00:00
header [ "Authorization" ] = rc . Authorization
2016-12-26 01:16:37 +00:00
}
if download {
2021-04-08 22:25:57 +00:00
rep . Actions [ "download" ] = & lfs_module . Link { Href : rc . ObjectLink ( pointer . Oid ) , Header : header }
2016-12-26 01:16:37 +00:00
}
if upload {
2021-04-08 22:25:57 +00:00
rep . Actions [ "upload" ] = & lfs_module . Link { Href : rc . ObjectLink ( pointer . Oid ) , Header : header }
2016-12-26 01:16:37 +00:00
}
2017-11-08 13:04:19 +00:00
if upload && ! download {
// Force client side verify action while gitea lacks proper server side verification
2019-05-24 21:21:00 +00:00
verifyHeader := make ( map [ string ] string )
for k , v := range header {
verifyHeader [ k ] = v
}
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
2021-04-08 22:25:57 +00:00
verifyHeader [ "Accept" ] = lfs_module . MediaType
2019-05-24 21:21:00 +00:00
2021-04-08 22:25:57 +00:00
rep . Actions [ "verify" ] = & lfs_module . Link { Href : rc . VerifyLink ( ) , Header : verifyHeader }
2017-11-08 13:04:19 +00:00
}
2016-12-26 01:16:37 +00:00
return rep
}
// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
2021-04-08 22:25:57 +00:00
// an Accept header with the lfs_module.MediaType
2021-01-26 15:36:53 +00:00
func MetaMatcher ( r * http . Request ) bool {
2016-12-26 01:16:37 +00:00
mediaParts := strings . Split ( r . Header . Get ( "Accept" ) , ";" )
mt := mediaParts [ 0 ]
2021-04-08 22:25:57 +00:00
return mt == lfs_module . MediaType
2016-12-26 01:16:37 +00:00
}
2021-04-08 22:25:57 +00:00
func unpack ( ctx * context . Context ) ( * requestContext , lfs_module . Pointer ) {
2016-12-26 01:16:37 +00:00
r := ctx . Req
2021-04-08 22:25:57 +00:00
rc := & requestContext {
2016-12-26 01:16:37 +00:00
User : ctx . Params ( "username" ) ,
Repo : strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" ) ,
Authorization : r . Header . Get ( "Authorization" ) ,
}
2021-04-08 22:25:57 +00:00
p := lfs_module . Pointer { Oid : ctx . Params ( "oid" ) }
2016-12-26 01:16:37 +00:00
if r . Method == "POST" { // Maybe also check if +json
2021-04-08 22:25:57 +00:00
var p2 lfs_module . Pointer
2021-01-26 15:36:53 +00:00
bodyReader := r . Body
2019-10-10 17:42:28 +00:00
defer bodyReader . Close ( )
2021-03-01 21:08:10 +00:00
json := jsoniter . ConfigCompatibleWithStandardLibrary
2019-10-10 17:42:28 +00:00
dec := json . NewDecoder ( bodyReader )
2021-04-08 22:25:57 +00:00
err := dec . Decode ( & p2 )
2016-12-26 01:16:37 +00:00
if err != nil {
2020-03-09 19:56:18 +00:00
// The error is logged as a WARN here because this may represent misbehaviour rather than a true error
2021-04-08 22:25:57 +00:00
log . Warn ( "Unable to decode POST request vars for LFS OID[%s] in %s/%s: Error: %v" , p . Oid , rc . User , rc . Repo , err )
return rc , p
2016-12-26 01:16:37 +00:00
}
2021-04-08 22:25:57 +00:00
p . Oid = p2 . Oid
p . Size = p2 . Size
2016-12-26 01:16:37 +00:00
}
2021-04-08 22:25:57 +00:00
return rc , p
2016-12-26 01:16:37 +00:00
}
// TODO cheap hack, unify with unpack
2021-04-08 22:25:57 +00:00
func unpackbatch ( ctx * context . Context ) * lfs_module . BatchRequest {
2016-12-26 01:16:37 +00:00
r := ctx . Req
2021-04-08 22:25:57 +00:00
var bv lfs_module . BatchRequest
2016-12-26 01:16:37 +00:00
2021-01-26 15:36:53 +00:00
bodyReader := r . Body
2019-10-10 17:42:28 +00:00
defer bodyReader . Close ( )
2021-03-01 21:08:10 +00:00
json := jsoniter . ConfigCompatibleWithStandardLibrary
2019-10-10 17:42:28 +00:00
dec := json . NewDecoder ( bodyReader )
2016-12-26 01:16:37 +00:00
err := dec . Decode ( & bv )
if err != nil {
2020-03-09 19:56:18 +00:00
// The error is logged as a WARN here because this may represent misbehaviour rather than a true error
log . Warn ( "Unable to decode BATCH request vars in %s/%s: Error: %v" , ctx . Params ( "username" ) , strings . TrimSuffix ( ctx . Params ( "reponame" ) , ".git" ) , err )
2016-12-26 01:16:37 +00:00
return & bv
}
return & bv
}
func writeStatus ( ctx * context . Context , status int ) {
message := http . StatusText ( status )
mediaParts := strings . Split ( ctx . Req . Header . Get ( "Accept" ) , ";" )
mt := mediaParts [ 0 ]
if strings . HasSuffix ( mt , "+json" ) {
message = ` { "message":" ` + message + ` "} `
}
ctx . Resp . WriteHeader ( status )
fmt . Fprint ( ctx . Resp , message )
logRequest ( ctx . Req , status )
}
2021-01-26 15:36:53 +00:00
func logRequest ( r * http . Request , status int ) {
2016-12-26 01:16:37 +00:00
log . Debug ( "LFS request - Method: %s, URL: %s, Status %d" , r . Method , r . URL , status )
}
// authenticate uses the authorization string to determine whether
// or not to proceed. This server assumes an HTTP Basic auth format.
func authenticate ( ctx * context . Context , repository * models . Repository , authorization string , requireWrite bool ) bool {
accessMode := models . AccessModeRead
if requireWrite {
accessMode = models . AccessModeWrite
}
2019-01-31 13:36:57 +00:00
// ctx.IsSigned is unnecessary here, this will be checked in perm.CanAccess
2018-11-28 11:26:14 +00:00
perm , err := models . GetUserRepoPermission ( repository , ctx . User )
if err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v" , ctx . User , repository )
2018-11-28 11:26:14 +00:00
return false
2016-12-26 01:16:37 +00:00
}
2019-01-31 13:36:57 +00:00
canRead := perm . CanAccess ( accessMode , models . UnitTypeCode )
if canRead {
return true
2016-12-26 01:16:37 +00:00
}
2018-01-27 16:48:15 +00:00
user , repo , opStr , err := parseToken ( authorization )
2016-12-26 01:16:37 +00:00
if err != nil {
2020-03-09 19:56:18 +00:00
// Most of these are Warn level - the true internal server errors are logged in parseToken already
log . Warn ( "Authentication failure for provided token with Error: %v" , err )
2016-12-26 01:16:37 +00:00
return false
}
2018-01-27 16:48:15 +00:00
ctx . User = user
if opStr == "basic" {
2018-11-28 11:26:14 +00:00
perm , err = models . GetUserRepoPermission ( repository , ctx . User )
if err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v" , ctx . User , repository )
2018-11-28 11:26:14 +00:00
return false
}
return perm . CanAccess ( accessMode , models . UnitTypeCode )
2016-12-26 01:16:37 +00:00
}
2018-01-27 16:48:15 +00:00
if repository . ID == repo . ID {
if requireWrite && opStr != "upload" {
return false
}
return true
2016-12-26 01:16:37 +00:00
}
2018-01-27 16:48:15 +00:00
return false
2016-12-26 01:16:37 +00:00
}
2018-01-27 16:48:15 +00:00
func parseToken ( authorization string ) ( * models . User , * models . Repository , string , error ) {
if authorization == "" {
return nil , nil , "unknown" , fmt . Errorf ( "No token" )
}
if strings . HasPrefix ( authorization , "Bearer " ) {
2020-03-09 19:56:18 +00:00
token , err := jwt . ParseWithClaims ( authorization [ 7 : ] , & Claims { } , func ( t * jwt . Token ) ( interface { } , error ) {
2018-01-27 16:48:15 +00:00
if _ , ok := t . Method . ( * jwt . SigningMethodHMAC ) ; ! ok {
return nil , fmt . Errorf ( "unexpected signing method: %v" , t . Header [ "alg" ] )
}
return setting . LFS . JWTSecretBytes , nil
} )
if err != nil {
2020-03-09 19:56:18 +00:00
// The error here is WARN level because it is caused by bad authorization rather than an internal server error
2018-01-27 16:48:15 +00:00
return nil , nil , "unknown" , err
2016-12-26 01:16:37 +00:00
}
2020-03-09 19:56:18 +00:00
claims , claimsOk := token . Claims . ( * Claims )
2018-01-27 16:48:15 +00:00
if ! token . Valid || ! claimsOk {
return nil , nil , "unknown" , fmt . Errorf ( "Token claim invalid" )
}
2020-03-09 19:56:18 +00:00
r , err := models . GetRepositoryByID ( claims . RepoID )
2018-01-27 16:48:15 +00:00
if err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Unable to GetRepositoryById[%d]: Error: %v" , claims . RepoID , err )
return nil , nil , claims . Op , err
2018-01-27 16:48:15 +00:00
}
2020-03-09 19:56:18 +00:00
u , err := models . GetUserByID ( claims . UserID )
2018-01-27 16:48:15 +00:00
if err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Unable to GetUserById[%d]: Error: %v" , claims . UserID , err )
return nil , r , claims . Op , err
2018-01-27 16:48:15 +00:00
}
2020-03-09 19:56:18 +00:00
return u , r , claims . Op , nil
2016-12-26 01:16:37 +00:00
}
2018-01-27 16:48:15 +00:00
if strings . HasPrefix ( authorization , "Basic " ) {
c , err := base64 . StdEncoding . DecodeString ( strings . TrimPrefix ( authorization , "Basic " ) )
if err != nil {
return nil , nil , "basic" , err
}
cs := string ( c )
i := strings . IndexByte ( cs , ':' )
if i < 0 {
return nil , nil , "basic" , fmt . Errorf ( "Basic auth invalid" )
}
user , password := cs [ : i ] , cs [ i + 1 : ]
u , err := models . GetUserByName ( user )
if err != nil {
2020-03-09 19:56:18 +00:00
log . Error ( "Unable to GetUserByName[%d]: Error: %v" , user , err )
2018-01-27 16:48:15 +00:00
return nil , nil , "basic" , err
}
2019-01-30 21:18:54 +00:00
if ! u . IsPasswordSet ( ) || ! u . ValidatePassword ( password ) {
2018-01-27 16:48:15 +00:00
return nil , nil , "basic" , fmt . Errorf ( "Basic auth failed" )
}
return u , nil , "basic" , nil
2016-12-26 01:16:37 +00:00
}
2018-01-27 16:48:15 +00:00
return nil , nil , "unknown" , fmt . Errorf ( "Token not found" )
2016-12-26 01:16:37 +00:00
}
func requireAuth ( ctx * context . Context ) {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , "Basic realm=gitea-lfs" )
writeStatus ( ctx , 401 )
}