diff --git a/code/go/0chain.net/blobbercore/convert/convert.go b/code/go/0chain.net/blobbercore/convert/convert.go index ab399015f..0db96ed93 100644 --- a/code/go/0chain.net/blobbercore/convert/convert.go +++ b/code/go/0chain.net/blobbercore/convert/convert.go @@ -6,7 +6,6 @@ import ( "encoding/json" "mime/multipart" "net/http" - "strings" "time" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/readmarker" @@ -483,57 +482,19 @@ func WriteFileGRPCToHTTP(req *blobbergrpc.UploadFileRequest) (*http.Request, err } func DownloadFileGRPCToHTTP(req *blobbergrpc.DownloadFileRequest) (*http.Request, error) { - body := bytes.NewBuffer([]byte{}) - writer := multipart.NewWriter(body) - err := writer.WriteField("path", req.Path) + r, err := http.NewRequest("GET", "", nil) if err != nil { return nil, err } - err = writer.WriteField("path_hash", req.PathHash) - if err != nil { - return nil, err - } - - err = writer.WriteField("rx_pay", req.RxPay) - if err != nil { - return nil, err - } - - err = writer.WriteField("block_num", req.BlockNum) - if err != nil { - return nil, err - } - - err = writer.WriteField("num_blocks", req.NumBlocks) - if err != nil { - return nil, err - } - - err = writer.WriteField("read_marker", req.ReadMarker) - if err != nil { - return nil, err - } - - err = writer.WriteField("auth_token", req.AuthToken) - if err != nil { - return nil, err - } - - err = writer.WriteField("content", req.Content) - if err != nil { - return nil, err - } - - writer.Close() - - r, err := http.NewRequest("POST", "", strings.NewReader(body.String())) - if err != nil { - return nil, err - } - - r.Header.Set("Content-Type", writer.FormDataContentType()) - + r.Header.Set("X-Path", req.Path) + r.Header.Set("X-Path-Hash", req.PathHash) + r.Header.Set("X-Rxpay", req.RxPay) + r.Header.Set("X-Block-Num", req.BlockNum) + r.Header.Set("X-Num-Blocks", req.NumBlocks) + r.Header.Set("X-Read-Marker", req.ReadMarker) + r.Header.Set("X-Auth-Token", req.AuthToken) + r.Header.Set("X-Mode", req.Content) return r, nil } diff --git a/code/go/0chain.net/blobbercore/handler/context.go b/code/go/0chain.net/blobbercore/handler/context.go index 2475b8527..0f27146a5 100644 --- a/code/go/0chain.net/blobbercore/handler/context.go +++ b/code/go/0chain.net/blobbercore/handler/context.go @@ -71,15 +71,7 @@ func WithHandler(handler func(ctx *Context) (interface{}, error)) func(w http.Re return } - if r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch { - ct := r.Header.Get("Content-Type") - if ct == "application/x-www-form-urlencoded" { - r.ParseForm() //nolint: errcheck - } else { - r.ParseMultipartForm(FormFileParseMaxMemory) //nolint: errcheck - } - - } + TryParseForm(r) w.Header().Set("Access-Control-Allow-Origin", "*") // CORS for all. w.Header().Set("Content-Type", "application/json") diff --git a/code/go/0chain.net/blobbercore/handler/download_request_header.go b/code/go/0chain.net/blobbercore/handler/download_request_header.go new file mode 100644 index 000000000..1503fd28a --- /dev/null +++ b/code/go/0chain.net/blobbercore/handler/download_request_header.go @@ -0,0 +1,113 @@ +package handler + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/readmarker" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/reference" + "github.com/0chain/blobber/code/go/0chain.net/core/common" + "github.com/0chain/errors" +) + +// DownloadRequestHeader metedata of download request +type DownloadRequestHeader struct { + req *http.Request + allocationID string + PathHash string + Path string + BlockNum int64 + NumBlocks int64 + ReadMarker readmarker.ReadMarker + AuthToken string + RxPay bool + DownloadMode string +} + +func FromDownloadRequest(allocationID string, req *http.Request) (*DownloadRequestHeader, error) { + if allocationID == "" { + return nil, errors.Throw(common.ErrInvalidParameter, "allocationID") + } + + if req == nil { + return nil, errors.Throw(common.ErrInvalidParameter, "req") + } + + dr := &DownloadRequestHeader{ + allocationID: allocationID, + req: req, + } + + err := dr.Parse() + if err != nil { + return nil, err + } + + return dr, nil +} + +func (dr *DownloadRequestHeader) Parse() error { + if dr.req == nil { + return errors.Throw(common.ErrInvalidParameter, "req") + } + + pathHash := dr.Get("X-Path-Hash") + path := dr.Get("X-Path") + if pathHash == "" { + if path == "" { + return errors.Throw(common.ErrInvalidParameter, "X-Path") + } + pathHash = reference.GetReferenceLookup(dr.allocationID, path) + } + + dr.PathHash = pathHash + dr.Path = path + + blockNum := dr.GetInt64("X-Block-Num", -1) + if blockNum <= 0 { + return errors.Throw(common.ErrInvalidParameter, "X-Block-Num") + } + dr.BlockNum = blockNum + + numBlocks := dr.GetInt64("X-Num-Blocks", 1) + if numBlocks <= 0 { + return errors.Throw(common.ErrInvalidParameter, "X-Num-Blocks") + } + dr.NumBlocks = numBlocks + + readMarker := dr.Get("X-Read-Marker") + + if readMarker == "" { + return errors.Throw(common.ErrInvalidParameter, "X-Read-Marker") + } + + err := json.Unmarshal([]byte(readMarker), &dr.ReadMarker) + if err != nil { + return errors.Throw(common.ErrInvalidParameter, "X-Read-Marker") + } + + dr.AuthToken = dr.Get("X-Auth-Token") + + dr.RxPay = dr.Get("X-Rxpay") == "true" + dr.DownloadMode = dr.Get("X-Mode") + + return nil +} + +func (dr *DownloadRequestHeader) Get(key string) string { + if dr.req == nil { + return "" + } + return dr.req.Header.Get(key) +} + +func (dr *DownloadRequestHeader) GetInt64(key string, defaultValue int64) int64 { + v := dr.Get(key) + i, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return defaultValue + } + + return i +} diff --git a/code/go/0chain.net/blobbercore/handler/handler_download_test.go b/code/go/0chain.net/blobbercore/handler/handler_download_test.go new file mode 100644 index 000000000..1fe163206 --- /dev/null +++ b/code/go/0chain.net/blobbercore/handler/handler_download_test.go @@ -0,0 +1,1076 @@ +package handler + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "regexp" + "testing" + "time" + + "github.com/0chain/gosdk/core/zcncrypto" + "github.com/0chain/gosdk/zboxcore/client" + zencryption "github.com/0chain/gosdk/zboxcore/encryption" + "github.com/0chain/gosdk/zboxcore/fileref" + "github.com/0chain/gosdk/zboxcore/marker" + "github.com/DATA-DOG/go-sqlmock" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/allocation" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/datastore" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/filestore" + "github.com/0chain/blobber/code/go/0chain.net/core/common" + "github.com/0chain/blobber/code/go/0chain.net/core/encryption" +) + +func setupDownloadHandlers() (*mux.Router, map[string]string) { + router := mux.NewRouter() + + dPath := "/v1/file/download/{allocation}" + dName := "Download" + router.HandleFunc(dPath, common.UserRateLimit( + common.ToJSONResponse( + WithConnection(DownloadHandler), + ), + ), + ).Name(dName) + + return router, map[string]string{ + dPath: dName, + } +} + +func getEncryptionScheme(mnemonic string) (zencryption.EncryptionScheme, error) { + encscheme := zencryption.NewEncryptionScheme() + if _, err := encscheme.Initialize(mnemonic); err != nil { + return nil, err + } + encscheme.InitForEncryption("filetype:audio") + return encscheme, nil +} + +func TestHandlers_Download_Requiring_Signature(t *testing.T) { + setup(t) + + clientJson := `{"client_id":"2f34516ed8c567089b7b5572b12950db34a62a07e16770da14b15b170d0d60a9","client_key":"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083","keys":[{"public_key":"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083","private_key":"9fef6ff5edc39a79c1d8e5eb7ca7e5ac14d34615ee49e6d8ca12ecec136f5907"}],"mnemonics":"expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe","version":"1.0","date_created":"2021-05-30 17:45:06.492093 +0545 +0545 m=+0.139083805"}` + guestClientJson := `{"client_id":"213297e22c8282ff85d1d5c99f4967636fe68f842c1351b24bd497246cbd26d9","client_key":"7710b547897e0bddf93a28903875b244db4d320e4170172b19a5d51280c73522e9bb381b184fa3d24d6e1464882bf7f89d24ac4e8d05616d55eb857a6e235383","keys":[{"public_key":"7710b547897e0bddf93a28903875b244db4d320e4170172b19a5d51280c73522e9bb381b184fa3d24d6e1464882bf7f89d24ac4e8d05616d55eb857a6e235383","private_key":"19ca446f814dcd56e28e11d4147f73590a07c7f1a9a6012087808a8602024a08"}],"mnemonics":"crazy dutch object arrest jump fragile oak amateur taxi trigger gap aspect marriage hat slice wool island spike unlock alter include easily say ramp","version":"1.0","date_created":"2022-01-26T07:26:41+05:45"}` + + require.NoError(t, client.PopulateClients([]string{clientJson, guestClientJson}, "bls0chain")) + clients := client.GetClients() + + ownerClient, guestClient := clients[0], clients[1] + + ownerScheme, err := getEncryptionScheme(ownerClient.Mnemonic) + if err != nil { + t.Fatal(err) + } + + guestScheme, err := getEncryptionScheme(guestClient.Mnemonic) + if err != nil { + t.Fatal(err) + } + // require.NoError(t, client.PopulateClient(clientJson, "bls0chain")) + // setupEncryptionScheme() + + router, handlers := setupDownloadHandlers() + + sch := zcncrypto.NewSignatureScheme("bls0chain") + //sch.Mnemonic = "expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe" + _, err = sch.RecoverKeys("expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe") + if err != nil { + t.Fatal(err) + } + + ts := time.Now().Add(time.Hour) + alloc := makeTestAllocation(common.Timestamp(ts.Unix())) + alloc.OwnerPublicKey = ownerClient.Keys[0].PublicKey + alloc.OwnerID = ownerClient.ClientID + + type ( + args struct { + w *httptest.ResponseRecorder + r *http.Request + } + test struct { + name string + args args + alloc *allocation.Allocation + setupDbMock func(mock sqlmock.Sqlmock) + begin func() + end func() + wantCode int + wantBody string + } + ) + negativeTests := make([]test, 0) + for _, name := range handlers { + if !isEndpointRequireSignature(name) { + continue + } + + baseSetupDbMock := func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows([]string{"id", "tx", "expiration_date", "owner_public_key", "owner_id"}). + AddRow(alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + mock.ExpectCommit() + } + + emptySignature := test{ + name: name + "_Empty_Signature", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + url, err := router.Get(name).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + method := http.MethodGet + if !isEndpointAllowGetReq(name) { + method = http.MethodPost + } + r, err := http.NewRequest(method, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + return r + }(), + }, + alloc: alloc, + setupDbMock: baseSetupDbMock, + wantCode: http.StatusBadRequest, + wantBody: "{\"code\":\"invalid_signature\",\"error\":\"invalid_signature: Invalid signature\"}\n\n", + } + negativeTests = append(negativeTests, emptySignature) + + wrongSignature := test{ + name: name + "_Wrong_Signature", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + url, err := router.Get(name).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + method := http.MethodGet + if !isEndpointAllowGetReq(name) { + method = http.MethodPost + } + r, err := http.NewRequest(method, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash("another data") + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + return r + }(), + }, + alloc: alloc, + setupDbMock: baseSetupDbMock, + wantCode: http.StatusBadRequest, + wantBody: "{\"code\":\"invalid_signature\",\"error\":\"invalid_signature: Invalid signature\"}\n\n", + } + negativeTests = append(negativeTests, wrongSignature) + } + + positiveTests := []test{ + { + name: "DownloadFile_Record_Not_Found", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + remotePath := "/file.txt" + + rm := &marker.ReadMarker{} + rm.ClientID = ownerClient.ClientID + rm.ClientPublicKey = ownerClient.ClientKey + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.OwnerID = ownerClient.ClientID + rm.Signature, err = signHash(ownerClient, rm.GetHash()) + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + require.NoError(t, err) + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + require.NoError(t, err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Path-Hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + r.Header.Set("X-Block-Num", fmt.Sprintf("%d", 1)) + r.Header.Set("X-Read-Marker", string(rmData)) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash).WillReturnError(gorm.ErrRecordNotFound) + + }, + wantCode: http.StatusBadRequest, + wantBody: "{\"code\":\"download_file\",\"error\":\"download_file: invalid file path: record not found\"}\n\n", + }, + { + name: "DownloadFile_Unencrypted_return_file", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + remotePath := "/file.txt" + + rm := &marker.ReadMarker{} + rm.ClientID = ownerClient.ClientID + rm.ClientPublicKey = ownerClient.ClientKey + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = ownerClient.ClientID + rm.Signature, err = signHash(ownerClient, rm.GetHash()) + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + require.NoError(t, err) + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Path-Hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + r.Header.Set("X-Block-Num", fmt.Sprintf("%d", 1)) + r.Header.Set("X-Read-Marker", string(rmData)) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "lookup_hash", "content_hash"}). + AddRow("/file.txt", "f", filePathHash, "abcd"), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). + WithArgs(ownerClient.ClientID). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(ownerClient.ClientID). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(ownerClient.ClientID), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`(UPDATE "read_markers" SET)(.+)`). + WithArgs(ownerClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + wantBody: "\"bW9jaw==\"\n", //base64encoded for mock string + }, + { + name: "DownloadFile_Encrypted_Permission_Denied_Unshared_File", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + remotePath := "/file.txt" + + pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) + authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, ownerClient.Keys[0].PublicKey) + if err != nil { + t.Fatal(err) + } + + rm := &marker.ReadMarker{} + rm.ClientID = guestClient.ClientID + rm.ClientPublicKey = guestClient.ClientKey + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = ownerClient.ClientID + rm.Signature, err = signHash(guestClient, rm.GetHash()) + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + require.NoError(t, err) + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Read-Marker", string(rmData)) + r.Header.Set("X-Path-Hash", pathHash) + r.Header.Set("X-Block-Num", fmt.Sprintf("%d", 1)) + r.Header.Set("X-Auth-Token", authTicket) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, guestClient.ClientID) + r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "chunk_size"}). + AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", "qCj3sXXeXUAByi1ERIbcfXzWN75dyocYzyRXnkStXio=", 65536), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(client.GetClientID()), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "read_markers"`). + WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs(client.GetClientID(), filePathHash). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectCommit() + }, + wantCode: http.StatusBadRequest, + wantBody: "{\"error\":\"client does not have permission to download the file. share does not exist\"}\n\n", + }, + { + name: "DownloadFile_Encrypted_Permission_Allowed_shared_File", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + remotePath := "/file.txt" + + pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) + authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") + if err != nil { + t.Fatal(err) + } + rm := &marker.ReadMarker{} + rm.ClientID = guestClient.ClientID + rm.ClientPublicKey = guestClient.ClientKey + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = ownerClient.ClientID + rm.Signature, err = signHash(guestClient, rm.GetHash()) + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + require.NoError(t, err) + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Read-Marker", string(rmData)) + r.Header.Set("X-Path-Hash", pathHash) + r.Header.Set("X-Block-Num", fmt.Sprintf("%d", 1)) + r.Header.Set("X-Auth-Token", authTicket) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, guestClient.ClientID) + r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) + + return r + }(), + }, + alloc: alloc, + begin: func() { + dataToEncrypt := "data_to_encrypt" + encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) + if err != nil { + t.Fatal(err) + } + + header := make([]byte, EncryptionHeaderSize) + copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) + data := append(header, encMsg.EncryptedData...) + fmt.Println("Encrypted data: ", string(data)) + setMockFileBlock(data) + }, + end: func() { + resetMockFileBlock() + }, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "chunk_size"}). + AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), 65536), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). + WithArgs(guestClient.ClientID). + WillReturnError(gorm.ErrRecordNotFound) + + guestPublicEncryptedKey, err := guestScheme.GetPublicKey() + if err != nil { + t.Fatal(err) + } + reEncryptionKey, err := ownerScheme.GetReGenKey(guestPublicEncryptedKey, "filetype:audio") + + if err != nil { + t.Fatal(err) + } + fmt.Printf("\n\nencryptedKey: %v\tgpbk: %v\treKey: %v\n\n", ownerScheme.GetEncryptedKey(), guestPublicEncryptedKey, reEncryptionKey) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs(guestClient.ClientID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"re_encryption_key", "client_encryption_public_key"}). + AddRow(reEncryptionKey, guestPublicEncryptedKey), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(guestClient.ClientID). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(guestClient.ClientID), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "read_markers"`). + WithArgs(guestClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + wantBody: "", + }, + { + name: "DownloadFile_Encrypted_InSharedFolder_Permission_Allowed_shared_File", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + remotePath := "/" + pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") + if err != nil { + t.Fatal(err) + } + rm := &marker.ReadMarker{} + rm.ClientID = guestClient.ClientID + rm.ClientPublicKey = guestClient.ClientKey + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = ownerClient.ClientID + rm.Signature, err = signHash(guestClient, rm.GetHash()) + if err != nil { + t.Fatal(err) + } + + rmData, err := json.Marshal(rm) + require.NoError(t, err) + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Path-Hash", filePathHash) + r.Header.Set("X-Block-Num", fmt.Sprintf("%d", 1)) + r.Header.Set("X-Auth-Token", authTicket) + r.Header.Set("X-Read-Marker", string(rmData)) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, guestClient.ClientID) + r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) + + return r + }(), + }, + alloc: alloc, + begin: func() { + dataToEncrypt := "data_to_encrypt" + encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) + if err != nil { + t.Fatal(err) + } + + header := make([]byte, EncryptionHeaderSize) + copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) + data := append(header, encMsg.EncryptedData...) + setMockFileBlock(data) + }, + end: func() { + resetMockFileBlock() + }, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path", "chunk_size"}). + AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), "/", fileref.CHUNK_SIZE), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). + WithArgs(guestClient.ClientID). + WillReturnError(gorm.ErrRecordNotFound) + + rootPathHash := fileref.GetReferenceLookup(alloc.Tx, "/") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, rootPathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path"}). + AddRow("/", "d", rootPathHash, rootPathHash, "content_hash", "", "."), + ) + + gpbk, err := guestScheme.GetPublicKey() + if err != nil { + t.Fatal(err) + } + + reEncryptionKey, _ := ownerScheme.GetReGenKey(gpbk, "filetype:audio") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs(guestClient.ClientID, rootPathHash). + WillReturnRows( + sqlmock.NewRows([]string{"re_encryption_key", "client_encryption_public_key"}). + AddRow(reEncryptionKey, gpbk), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(guestClient.ClientID). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(guestClient.ClientID), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "read_markers"`). + WithArgs(guestClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + wantBody: "", + }, + { + name: "DownloadFile_Encrypted_InSharedFolderSubdirectory_Permission_Allowed_shared_File", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + remotePath := "/folder1" + pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1/subfolder1/file.txt") + authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") + if err != nil { + t.Fatal(err) + } + rm := &marker.ReadMarker{} + rm.ClientID = guestClient.ClientID + rm.ClientPublicKey = guestClient.ClientKey + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = alloc.OwnerID + rm.Signature, err = signHash(guestClient, rm.GetHash()) + if err != nil { + t.Fatal(err) + } + + rmData, err := json.Marshal(rm) + require.NoError(t, err) + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Path-Hash", filePathHash) + r.Header.Set("X-Block-Num", fmt.Sprintf("%d", 1)) + r.Header.Set("X-Auth-Token", authTicket) + r.Header.Set("X-Read-Marker", string(rmData)) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, guestClient.ClientID) + r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) + + return r + }(), + }, + alloc: alloc, + begin: func() { + dataToEncrypt := "data_to_encrypt" + encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) + if err != nil { + t.Fatal(err) + } + + header := make([]byte, EncryptionHeaderSize) + copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) + data := append(header, encMsg.EncryptedData...) + setMockFileBlock(data) + }, + end: func() { + resetMockFileBlock() + }, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1/subfolder1/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path", "chunk_size"}). + AddRow("/folder1/subfolder1/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), "/folder1/subfolder1", filestore.CHUNK_SIZE), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). + WithArgs(guestClient.ClientID). + WillReturnError(gorm.ErrRecordNotFound) + + rootPathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, rootPathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path"}). + AddRow("/folder1", "d", rootPathHash, rootPathHash, "content_hash", "", "."), + ) + + gpbk, err := guestScheme.GetPublicKey() + if err != nil { + t.Fatal(err) + } + + reEncryptionKey, _ := ownerScheme.GetReGenKey(gpbk, "filetype:audio") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs(guestClient.ClientID, rootPathHash). + WillReturnRows( + sqlmock.NewRows([]string{"re_encryption_key", "client_encryption_public_key"}). + AddRow(reEncryptionKey, gpbk), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(guestClient.ClientID). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(guestClient.ClientID), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "read_markers"`). + WithArgs(guestClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + wantBody: "", + }, + { + name: "DownloadFile_Encrypted_InSharedFolder_WrongFilePath_Permission_Rejected_shared_File", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + remotePath := "/folder1" + pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder2/subfolder1/file.txt") + authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") + if err != nil { + t.Fatal(err) + } + rm := &marker.ReadMarker{} + rm.ClientID = guestClient.ClientID + rm.ClientPublicKey = guestClient.ClientKey + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = alloc.OwnerID + rm.Signature, err = signHash(guestClient, rm.GetHash()) + if err != nil { + t.Fatal(err) + } + + rmData, err := json.Marshal(rm) + require.NoError(t, err) + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("X-Read-Marker", string(rmData)) + r.Header.Set("X-Path-Hash", filePathHash) + r.Header.Set("X-Block-Num", fmt.Sprintf("%d", 1)) + r.Header.Set("X-Auth-Token", authTicket) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, guestClient.ClientID) + r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) + + return r + }(), + }, + alloc: alloc, + begin: func() { + dataToEncrypt := "data_to_encrypt" + encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) + if err != nil { + t.Fatal(err) + } + + header := make([]byte, EncryptionHeaderSize) + copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) + data := append(header, encMsg.EncryptedData...) + setMockFileBlock(data) + }, + end: func() { + resetMockFileBlock() + }, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder2/subfolder1/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path", "chunk_size"}). + AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), "/folder2/subfolder1", fileref.CHUNK_SIZE), + ) + + rootPathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, rootPathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path"}). + AddRow("/folder1", "d", rootPathHash, rootPathHash, "content_hash", "", "/"), + ) + + }, + wantCode: http.StatusBadRequest, + wantBody: "{\"code\":\"download_file\",\"error\":\"download_file: cannot verify auth ticket: invalid_parameters: Auth ticket is not valid for the resource being requested\"}\n\n", + }, + } + + tests := append(positiveTests, negativeTests...) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mock := datastore.MockTheStore(t) + test.setupDbMock(mock) + + if test.begin != nil { + test.begin() + } + router.ServeHTTP(test.args.w, test.args.r) + if test.end != nil { + test.end() + } + + fmt.Printf("\nResponse body: %v", test.args.w.Body.String()) + assert.Equal(t, test.wantCode, test.args.w.Result().StatusCode) + if test.wantCode != http.StatusOK || test.wantBody != "" { + assert.Equal(t, test.wantBody, test.args.w.Body.String()) + } + }) + } + + curDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if err := os.RemoveAll(curDir + "/tmp"); err != nil { + t.Fatal(err) + } +} diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 49615e78e..f09bd17d8 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -17,7 +17,6 @@ import ( "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zboxcore/client" - zencryption "github.com/0chain/gosdk/zboxcore/encryption" "github.com/0chain/gosdk/zboxcore/fileref" "github.com/0chain/gosdk/zboxcore/marker" "github.com/0chain/gosdk/zcncore" @@ -88,15 +87,6 @@ func signHash(client *client.Client, hash string) (string, error) { return retSignature, nil } -func getEncryptionScheme(mnemonic string) (zencryption.EncryptionScheme, error) { - encscheme := zencryption.NewEncryptionScheme() - if _, err := encscheme.Initialize(mnemonic); err != nil { - return nil, err - } - encscheme.InitForEncryption("filetype:audio") - return encscheme, nil -} - func init() { resetMockFileBlock() common.ConfigRateLimits() @@ -328,25 +318,13 @@ func TestHandlers_Requiring_Signature(t *testing.T) { require.NoError(t, client.PopulateClients([]string{clientJson, guestClientJson}, "bls0chain")) clients := client.GetClients() - ownerClient, guestClient := clients[0], clients[1] - - ownerScheme, err := getEncryptionScheme(ownerClient.Mnemonic) - if err != nil { - t.Fatal(err) - } - - guestScheme, err := getEncryptionScheme(guestClient.Mnemonic) - if err != nil { - t.Fatal(err) - } - // require.NoError(t, client.PopulateClient(clientJson, "bls0chain")) - // setupEncryptionScheme() + ownerClient := clients[0] router, handlers := setupHandlers() sch := zcncrypto.NewSignatureScheme("bls0chain") //sch.Mnemonic = "expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe" - _, err = sch.RecoverKeys("expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe") + _, err := sch.RecoverKeys("expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe") if err != nil { t.Fatal(err) } @@ -1495,904 +1473,9 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantCode: http.StatusOK, wantBody: "{\"message\":\"Path not found\",\"status\":404}\n", }, - { - name: "DownloadFile_Record_Not_Found", - args: args{ - w: httptest.NewRecorder(), - r: func() *http.Request { - handlerName := handlers["/v1/file/download/{allocation}"] - url, err := router.Get(handlerName).URL("allocation", alloc.Tx) - if err != nil { - t.Fatal() - } - - body := bytes.NewBuffer(nil) - formWriter := multipart.NewWriter(body) - remotePath := "/file.txt" - - require.NoError(t, formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath))) - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) - rm := &marker.ReadMarker{} - rm.ClientID = ownerClient.ClientID - rm.ClientPublicKey = ownerClient.ClientKey - rm.BlobberID = "" - rm.AllocationID = alloc.ID - rm.OwnerID = ownerClient.ClientID - rm.Signature, err = signHash(ownerClient, rm.GetHash()) - if err != nil { - t.Fatal(err) - } - rmData, err := json.Marshal(rm) - require.NoError(t, err) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) - if err := formWriter.Close(); err != nil { - t.Fatal(err) - } - r, err := http.NewRequest(http.MethodPost, url.String(), body) - r.Header.Add("Content-Type", formWriter.FormDataContentType()) - if err != nil { - t.Fatal(err) - } - - hash := encryption.Hash(alloc.Tx) - sign, err := sch.Sign(hash) - if err != nil { - t.Fatal(err) - } - - r.Header.Set("Content-Type", formWriter.FormDataContentType()) - r.Header.Set(common.ClientSignatureHeader, sign) - r.Header.Set(common.ClientHeader, alloc.OwnerID) - r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) - - return r - }(), - }, - alloc: alloc, - setupDbMock: func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). - WithArgs(alloc.Tx). - WillReturnRows( - sqlmock.NewRows( - []string{ - "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", - }, - ). - AddRow( - alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), - ), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). - WithArgs(alloc.ID). - WillReturnRows( - sqlmock.NewRows([]string{"id", "allocation_id"}). - AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), - ) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, filePathHash).WillReturnError(gorm.ErrRecordNotFound) - - }, - wantCode: http.StatusBadRequest, - wantBody: "{\"code\":\"download_file\",\"error\":\"download_file: invalid file path: record not found\"}\n\n", - }, - { - name: "DownloadFile_Unencrypted_return_file", - args: args{ - w: httptest.NewRecorder(), - r: func() *http.Request { - handlerName := handlers["/v1/file/download/{allocation}"] - url, err := router.Get(handlerName).URL("allocation", alloc.Tx) - if err != nil { - t.Fatal() - } - - body := bytes.NewBuffer(nil) - formWriter := multipart.NewWriter(body) - remotePath := "/file.txt" - - require.NoError(t, formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath))) - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) - rm := &marker.ReadMarker{} - rm.ClientID = ownerClient.ClientID - rm.ClientPublicKey = ownerClient.ClientKey - rm.BlobberID = "" - rm.AllocationID = alloc.ID - rm.ReadCounter = 1 - rm.OwnerID = ownerClient.ClientID - rm.Signature, err = signHash(ownerClient, rm.GetHash()) - if err != nil { - t.Fatal(err) - } - rmData, err := json.Marshal(rm) - require.NoError(t, err) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) - if err := formWriter.Close(); err != nil { - t.Fatal(err) - } - r, err := http.NewRequest(http.MethodPost, url.String(), body) - r.Header.Add("Content-Type", formWriter.FormDataContentType()) - if err != nil { - t.Fatal(err) - } - - hash := encryption.Hash(alloc.Tx) - sign, err := sch.Sign(hash) - if err != nil { - t.Fatal(err) - } - - r.Header.Set("Content-Type", formWriter.FormDataContentType()) - r.Header.Set(common.ClientSignatureHeader, sign) - r.Header.Set(common.ClientHeader, alloc.OwnerID) - r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) - - return r - }(), - }, - alloc: alloc, - setupDbMock: func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). - WithArgs(alloc.Tx). - WillReturnRows( - sqlmock.NewRows( - []string{ - "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", - }, - ). - AddRow( - alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), - ), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). - WithArgs(alloc.ID). - WillReturnRows( - sqlmock.NewRows([]string{"id", "allocation_id"}). - AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), - ) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, filePathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "lookup_hash", "content_hash"}). - AddRow("/file.txt", "f", filePathHash, "abcd"), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). - WithArgs(ownerClient.ClientID). - WillReturnError(gorm.ErrRecordNotFound) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). - WithArgs(ownerClient.ClientID). - WillReturnRows( - sqlmock.NewRows([]string{"client_id"}). - AddRow(ownerClient.ClientID), - ) - - aa := sqlmock.AnyArg() - - mock.ExpectExec(`(UPDATE "read_markers" SET)(.+)`). - WithArgs(ownerClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa). - WillReturnResult(sqlmock.NewResult(0, 0)) - - mock.ExpectCommit() - }, - wantCode: http.StatusOK, - wantBody: "\"bW9jaw==\"\n", //base64encoded for mock string - }, - { - name: "DownloadFile_Encrypted_Permission_Denied_Unshared_File", - args: args{ - w: httptest.NewRecorder(), - r: func() *http.Request { - handlerName := handlers["/v1/file/download/{allocation}"] - url, err := router.Get(handlerName).URL("allocation", alloc.Tx) - if err != nil { - t.Fatal() - } - - body := bytes.NewBuffer(nil) - formWriter := multipart.NewWriter(body) - remotePath := "/file.txt" - - pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) - require.NoError(t, formWriter.WriteField("path_hash", pathHash)) - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) - authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, ownerClient.Keys[0].PublicKey) - if err != nil { - t.Fatal(err) - } - - require.NoError(t, formWriter.WriteField("auth_token", authTicket)) - rm := &marker.ReadMarker{} - rm.ClientID = guestClient.ClientID - rm.ClientPublicKey = guestClient.ClientKey - rm.BlobberID = "" - rm.AllocationID = alloc.ID - rm.ReadCounter = 1 - rm.OwnerID = ownerClient.ClientID - rm.Signature, err = signHash(guestClient, rm.GetHash()) - if err != nil { - t.Fatal(err) - } - rmData, err := json.Marshal(rm) - require.NoError(t, err) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) - if err := formWriter.Close(); err != nil { - t.Fatal(err) - } - r, err := http.NewRequest(http.MethodPost, url.String(), body) - r.Header.Add("Content-Type", formWriter.FormDataContentType()) - if err != nil { - t.Fatal(err) - } - - hash := encryption.Hash(alloc.Tx) - sign, err := sch.Sign(hash) - if err != nil { - t.Fatal(err) - } - - r.Header.Set("Content-Type", formWriter.FormDataContentType()) - r.Header.Set(common.ClientSignatureHeader, sign) - r.Header.Set(common.ClientHeader, guestClient.ClientID) - r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) - - return r - }(), - }, - alloc: alloc, - setupDbMock: func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). - WithArgs(alloc.Tx). - WillReturnRows( - sqlmock.NewRows( - []string{ - "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", - }, - ). - AddRow( - alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), - ), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). - WithArgs(alloc.ID). - WillReturnRows( - sqlmock.NewRows([]string{"id", "allocation_id"}). - AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), - ) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, filePathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "chunk_size"}). - AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", "qCj3sXXeXUAByi1ERIbcfXzWN75dyocYzyRXnkStXio=", 65536), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). - WithArgs(client.GetClientID()). - WillReturnError(gorm.ErrRecordNotFound) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). - WithArgs(client.GetClientID()). - WillReturnRows( - sqlmock.NewRows([]string{"client_id"}). - AddRow(client.GetClientID()), - ) - - aa := sqlmock.AnyArg() - - mock.ExpectExec(`UPDATE "read_markers"`). - WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). - WillReturnResult(sqlmock.NewResult(0, 0)) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). - WithArgs(client.GetClientID(), filePathHash). - WillReturnError(gorm.ErrRecordNotFound) - - mock.ExpectCommit() - }, - wantCode: http.StatusBadRequest, - wantBody: "{\"error\":\"client does not have permission to download the file. share does not exist\"}\n\n", - }, - { - name: "DownloadFile_Encrypted_Permission_Allowed_shared_File", - args: args{ - w: httptest.NewRecorder(), - r: func() *http.Request { - handlerName := handlers["/v1/file/download/{allocation}"] - url, err := router.Get(handlerName).URL("allocation", alloc.Tx) - if err != nil { - t.Fatal() - } - - body := bytes.NewBuffer(nil) - formWriter := multipart.NewWriter(body) - remotePath := "/file.txt" - - pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) - require.NoError(t, formWriter.WriteField("path_hash", pathHash)) - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) - authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") - if err != nil { - t.Fatal(err) - } - require.NoError(t, formWriter.WriteField("auth_token", authTicket)) - rm := &marker.ReadMarker{} - rm.ClientID = guestClient.ClientID - rm.ClientPublicKey = guestClient.ClientKey - rm.BlobberID = "" - rm.AllocationID = alloc.ID - rm.ReadCounter = 1 - rm.OwnerID = ownerClient.ClientID - rm.Signature, err = signHash(guestClient, rm.GetHash()) - if err != nil { - t.Fatal(err) - } - rmData, err := json.Marshal(rm) - require.NoError(t, err) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) - if err := formWriter.Close(); err != nil { - t.Fatal(err) - } - r, err := http.NewRequest(http.MethodPost, url.String(), body) - r.Header.Add("Content-Type", formWriter.FormDataContentType()) - if err != nil { - t.Fatal(err) - } - - hash := encryption.Hash(alloc.Tx) - sign, err := sch.Sign(hash) - if err != nil { - t.Fatal(err) - } - - r.Header.Set("Content-Type", formWriter.FormDataContentType()) - r.Header.Set(common.ClientSignatureHeader, sign) - r.Header.Set(common.ClientHeader, guestClient.ClientID) - r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) - - return r - }(), - }, - alloc: alloc, - begin: func() { - dataToEncrypt := "data_to_encrypt" - encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) - if err != nil { - t.Fatal(err) - } - - header := make([]byte, EncryptionHeaderSize) - copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) - data := append(header, encMsg.EncryptedData...) - fmt.Println("Encrypted data: ", string(data)) - setMockFileBlock(data) - }, - end: func() { - resetMockFileBlock() - }, - setupDbMock: func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). - WithArgs(alloc.Tx). - WillReturnRows( - sqlmock.NewRows( - []string{ - "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", - }, - ). - AddRow( - alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), - ), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). - WithArgs(alloc.ID). - WillReturnRows( - sqlmock.NewRows([]string{"id", "allocation_id"}). - AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), - ) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, filePathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "chunk_size"}). - AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), 65536), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). - WithArgs(guestClient.ClientID). - WillReturnError(gorm.ErrRecordNotFound) - - guestPublicEncryptedKey, err := guestScheme.GetPublicKey() - if err != nil { - t.Fatal(err) - } - reEncryptionKey, err := ownerScheme.GetReGenKey(guestPublicEncryptedKey, "filetype:audio") - - if err != nil { - t.Fatal(err) - } - fmt.Printf("\n\nencryptedKey: %v\tgpbk: %v\treKey: %v\n\n", ownerScheme.GetEncryptedKey(), guestPublicEncryptedKey, reEncryptionKey) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). - WithArgs(guestClient.ClientID, filePathHash). - WillReturnRows( - sqlmock.NewRows([]string{"re_encryption_key", "client_encryption_public_key"}). - AddRow(reEncryptionKey, guestPublicEncryptedKey), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). - WithArgs(guestClient.ClientID). - WillReturnRows( - sqlmock.NewRows([]string{"client_id"}). - AddRow(guestClient.ClientID), - ) - - aa := sqlmock.AnyArg() - - mock.ExpectExec(`UPDATE "read_markers"`). - WithArgs(guestClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). - WillReturnResult(sqlmock.NewResult(0, 0)) - - mock.ExpectCommit() - }, - wantCode: http.StatusOK, - wantBody: "", - }, - { - name: "DownloadFile_Encrypted_InSharedFolder_Permission_Allowed_shared_File", - args: args{ - w: httptest.NewRecorder(), - r: func() *http.Request { - handlerName := handlers["/v1/file/download/{allocation}"] - url, err := router.Get(handlerName).URL("allocation", alloc.Tx) - if err != nil { - t.Fatal() - } - - body := bytes.NewBuffer(nil) - formWriter := multipart.NewWriter(body) - - remotePath := "/" - pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") - require.NoError(t, formWriter.WriteField("path_hash", filePathHash)) - - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) - authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") - if err != nil { - t.Fatal(err) - } - require.NoError(t, formWriter.WriteField("auth_token", authTicket)) - rm := &marker.ReadMarker{} - rm.ClientID = guestClient.ClientID - rm.ClientPublicKey = guestClient.ClientKey - rm.BlobberID = "" - rm.AllocationID = alloc.ID - rm.ReadCounter = 1 - rm.OwnerID = ownerClient.ClientID - rm.Signature, err = signHash(guestClient, rm.GetHash()) - if err != nil { - t.Fatal(err) - } - - rmData, err := json.Marshal(rm) - require.NoError(t, err) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) - if err := formWriter.Close(); err != nil { - t.Fatal(err) - } - - r, err := http.NewRequest(http.MethodPost, url.String(), body) - r.Header.Add("Content-Type", formWriter.FormDataContentType()) - if err != nil { - t.Fatal(err) - } - - hash := encryption.Hash(alloc.Tx) - sign, err := sch.Sign(hash) - if err != nil { - t.Fatal(err) - } - - r.Header.Set("Content-Type", formWriter.FormDataContentType()) - r.Header.Set(common.ClientSignatureHeader, sign) - r.Header.Set(common.ClientHeader, guestClient.ClientID) - r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) - - return r - }(), - }, - alloc: alloc, - begin: func() { - dataToEncrypt := "data_to_encrypt" - encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) - if err != nil { - t.Fatal(err) - } - - header := make([]byte, EncryptionHeaderSize) - copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) - data := append(header, encMsg.EncryptedData...) - setMockFileBlock(data) - }, - end: func() { - resetMockFileBlock() - }, - setupDbMock: func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). - WithArgs(alloc.Tx). - WillReturnRows( - sqlmock.NewRows( - []string{ - "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", - }, - ). - AddRow( - alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), - ), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). - WithArgs(alloc.ID). - WillReturnRows( - sqlmock.NewRows([]string{"id", "allocation_id"}). - AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), - ) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, filePathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path", "chunk_size"}). - AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), "/", fileref.CHUNK_SIZE), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). - WithArgs(guestClient.ClientID). - WillReturnError(gorm.ErrRecordNotFound) - - rootPathHash := fileref.GetReferenceLookup(alloc.Tx, "/") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, rootPathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path"}). - AddRow("/", "d", rootPathHash, rootPathHash, "content_hash", "", "."), - ) - - gpbk, err := guestScheme.GetPublicKey() - if err != nil { - t.Fatal(err) - } - - reEncryptionKey, _ := ownerScheme.GetReGenKey(gpbk, "filetype:audio") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). - WithArgs(guestClient.ClientID, rootPathHash). - WillReturnRows( - sqlmock.NewRows([]string{"re_encryption_key", "client_encryption_public_key"}). - AddRow(reEncryptionKey, gpbk), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). - WithArgs(guestClient.ClientID). - WillReturnRows( - sqlmock.NewRows([]string{"client_id"}). - AddRow(guestClient.ClientID), - ) - - aa := sqlmock.AnyArg() - - mock.ExpectExec(`UPDATE "read_markers"`). - WithArgs(guestClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). - WillReturnResult(sqlmock.NewResult(0, 0)) - - mock.ExpectCommit() - }, - wantCode: http.StatusOK, - wantBody: "", - }, - { - name: "DownloadFile_Encrypted_InSharedFolderSubdirectory_Permission_Allowed_shared_File", - args: args{ - w: httptest.NewRecorder(), - r: func() *http.Request { - handlerName := handlers["/v1/file/download/{allocation}"] - url, err := router.Get(handlerName).URL("allocation", alloc.Tx) - if err != nil { - t.Fatal() - } - - body := bytes.NewBuffer(nil) - formWriter := multipart.NewWriter(body) - - remotePath := "/folder1" - pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1/subfolder1/file.txt") - require.NoError(t, formWriter.WriteField("path_hash", filePathHash)) - - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) - authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") - if err != nil { - t.Fatal(err) - } - require.NoError(t, formWriter.WriteField("auth_token", authTicket)) - rm := &marker.ReadMarker{} - rm.ClientID = guestClient.ClientID - rm.ClientPublicKey = guestClient.ClientKey - rm.BlobberID = "" - rm.AllocationID = alloc.ID - rm.ReadCounter = 1 - rm.OwnerID = alloc.OwnerID - rm.Signature, err = signHash(guestClient, rm.GetHash()) - if err != nil { - t.Fatal(err) - } - - rmData, err := json.Marshal(rm) - require.NoError(t, err) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) - if err := formWriter.Close(); err != nil { - t.Fatal(err) - } - - r, err := http.NewRequest(http.MethodPost, url.String(), body) - r.Header.Add("Content-Type", formWriter.FormDataContentType()) - if err != nil { - t.Fatal(err) - } - - hash := encryption.Hash(alloc.Tx) - sign, err := sch.Sign(hash) - if err != nil { - t.Fatal(err) - } - - r.Header.Set("Content-Type", formWriter.FormDataContentType()) - r.Header.Set(common.ClientSignatureHeader, sign) - r.Header.Set(common.ClientHeader, guestClient.ClientID) - r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) - - return r - }(), - }, - alloc: alloc, - begin: func() { - dataToEncrypt := "data_to_encrypt" - encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) - if err != nil { - t.Fatal(err) - } - - header := make([]byte, EncryptionHeaderSize) - copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) - data := append(header, encMsg.EncryptedData...) - setMockFileBlock(data) - }, - end: func() { - resetMockFileBlock() - }, - setupDbMock: func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). - WithArgs(alloc.Tx). - WillReturnRows( - sqlmock.NewRows( - []string{ - "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", - }, - ). - AddRow( - alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), - ), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). - WithArgs(alloc.ID). - WillReturnRows( - sqlmock.NewRows([]string{"id", "allocation_id"}). - AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), - ) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1/subfolder1/file.txt") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, filePathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path", "chunk_size"}). - AddRow("/folder1/subfolder1/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), "/folder1/subfolder1", filestore.CHUNK_SIZE), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "collaborators" WHERE`)). - WithArgs(guestClient.ClientID). - WillReturnError(gorm.ErrRecordNotFound) - - rootPathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, rootPathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path"}). - AddRow("/folder1", "d", rootPathHash, rootPathHash, "content_hash", "", "."), - ) - - gpbk, err := guestScheme.GetPublicKey() - if err != nil { - t.Fatal(err) - } - - reEncryptionKey, _ := ownerScheme.GetReGenKey(gpbk, "filetype:audio") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). - WithArgs(guestClient.ClientID, rootPathHash). - WillReturnRows( - sqlmock.NewRows([]string{"re_encryption_key", "client_encryption_public_key"}). - AddRow(reEncryptionKey, gpbk), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). - WithArgs(guestClient.ClientID). - WillReturnRows( - sqlmock.NewRows([]string{"client_id"}). - AddRow(guestClient.ClientID), - ) - - aa := sqlmock.AnyArg() - - mock.ExpectExec(`UPDATE "read_markers"`). - WithArgs(guestClient.ClientKey, alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa, aa). - WillReturnResult(sqlmock.NewResult(0, 0)) - - mock.ExpectCommit() - }, - wantCode: http.StatusOK, - wantBody: "", - }, - { - name: "DownloadFile_Encrypted_InSharedFolder_WrongFilePath_Permission_Rejected_shared_File", - args: args{ - w: httptest.NewRecorder(), - r: func() *http.Request { - handlerName := handlers["/v1/file/download/{allocation}"] - url, err := router.Get(handlerName).URL("allocation", alloc.Tx) - if err != nil { - t.Fatal() - } - - body := bytes.NewBuffer(nil) - formWriter := multipart.NewWriter(body) - - remotePath := "/folder1" - pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder2/subfolder1/file.txt") - require.NoError(t, formWriter.WriteField("path_hash", filePathHash)) - - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) - authTicket, err := GetAuthTicketForEncryptedFile(ownerClient, alloc.ID, remotePath, pathHash, guestClient.ClientID, "") - if err != nil { - t.Fatal(err) - } - require.NoError(t, formWriter.WriteField("auth_token", authTicket)) - rm := &marker.ReadMarker{} - rm.ClientID = guestClient.ClientID - rm.ClientPublicKey = guestClient.ClientKey - rm.BlobberID = "" - rm.AllocationID = alloc.ID - rm.ReadCounter = 1 - rm.OwnerID = alloc.OwnerID - rm.Signature, err = signHash(guestClient, rm.GetHash()) - if err != nil { - t.Fatal(err) - } - - rmData, err := json.Marshal(rm) - require.NoError(t, err) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) - if err := formWriter.Close(); err != nil { - t.Fatal(err) - } - - r, err := http.NewRequest(http.MethodPost, url.String(), body) - r.Header.Add("Content-Type", formWriter.FormDataContentType()) - if err != nil { - t.Fatal(err) - } - - hash := encryption.Hash(alloc.Tx) - sign, err := sch.Sign(hash) - if err != nil { - t.Fatal(err) - } - - r.Header.Set("Content-Type", formWriter.FormDataContentType()) - r.Header.Set(common.ClientSignatureHeader, sign) - r.Header.Set(common.ClientHeader, guestClient.ClientID) - r.Header.Set(common.ClientKeyHeader, guestClient.ClientKey) - - return r - }(), - }, - alloc: alloc, - begin: func() { - dataToEncrypt := "data_to_encrypt" - encMsg, err := ownerScheme.Encrypt([]byte(dataToEncrypt)) - if err != nil { - t.Fatal(err) - } - - header := make([]byte, EncryptionHeaderSize) - copy(header, encMsg.MessageChecksum+encMsg.OverallChecksum) - data := append(header, encMsg.EncryptedData...) - setMockFileBlock(data) - }, - end: func() { - resetMockFileBlock() - }, - setupDbMock: func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). - WithArgs(alloc.Tx). - WillReturnRows( - sqlmock.NewRows( - []string{ - "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", - }, - ). - AddRow( - alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), - ), - ) - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). - WithArgs(alloc.ID). - WillReturnRows( - sqlmock.NewRows([]string{"id", "allocation_id"}). - AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), - ) - - filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder2/subfolder1/file.txt") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, filePathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path", "chunk_size"}). - AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", ownerScheme.GetEncryptedKey(), "/folder2/subfolder1", fileref.CHUNK_SIZE), - ) - - rootPathHash := fileref.GetReferenceLookup(alloc.Tx, "/folder1") - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(alloc.ID, rootPathHash). - WillReturnRows( - sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key", "parent_path"}). - AddRow("/folder1", "d", rootPathHash, rootPathHash, "content_hash", "", "/"), - ) - - }, - wantCode: http.StatusBadRequest, - wantBody: "{\"code\":\"download_file\",\"error\":\"download_file: cannot verify auth ticket: invalid_parameters: Auth ticket is not valid for the resource being requested\"}\n\n", - }, } tests := append(positiveTests, negativeTests...) - // tests := positiveTests for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/code/go/0chain.net/blobbercore/handler/helper.go b/code/go/0chain.net/blobbercore/handler/helper.go index 408aed256..651077ccb 100644 --- a/code/go/0chain.net/blobbercore/handler/helper.go +++ b/code/go/0chain.net/blobbercore/handler/helper.go @@ -1,6 +1,7 @@ package handler import ( + "net/http" "time" "github.com/0chain/blobber/code/go/0chain.net/core/common" @@ -15,3 +16,15 @@ func checkValidDate(s, dateLayOut string) error { } return nil } + +// TryParseForm try populates r.Form and r.PostForm. +func TryParseForm(r *http.Request) { + if r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch { + ct := r.Header.Get("Content-Type") + if ct == "application/x-www-form-urlencoded" { + r.ParseForm() //nolint: errcheck + } else { + r.ParseMultipartForm(FormFileParseMaxMemory) //nolint: errcheck + } + } +} diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 30d17f85d..4fd456ff1 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -192,54 +192,20 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) (r return nil, common.NewErrorf("download_file", "invalid allocation id passed: %v", err) } - if err = r.ParseMultipartForm(FormFileParseMaxMemory); err != nil { - Logger.Info("download_file - request_parse_error", zap.Error(err)) - return nil, common.NewErrorf("download_file", "request_parse_error: %v", err) - } - - pathHash, err := pathHashFromReq(r, alloc.ID) + dr, err := FromDownloadRequest(allocationTx, r) if err != nil { - return nil, common.NewError("download_file", "invalid path") - } - - var blockNumStr = r.FormValue("block_num") - if blockNumStr == "" { - return nil, common.NewError("download_file", "no block number") - } - - var blockNum int64 - blockNum, err = strconv.ParseInt(blockNumStr, 10, 64) - if err != nil || blockNum < 1 { - return nil, common.NewError("download_file", "invalid block number") - } - - numBlocks := int64(1) - numBlocksStr := r.FormValue("num_blocks") - - if numBlocksStr != "" { - numBlocks, err = strconv.ParseInt(numBlocksStr, 10, 64) - if err != nil || numBlocks <= 0 { - return nil, common.NewError("download_file", "invalid number of blocks") - } - } - - readMarkerString := r.FormValue("read_marker") - readMarker := new(readmarker.ReadMarker) - - if err := json.Unmarshal([]byte(readMarkerString), readMarker); err != nil { - return nil, common.NewErrorf("download_file", "invalid parameters, "+ - "error parsing the readmarker for download: %v", err) + return nil, err } rmObj := new(readmarker.ReadMarkerEntity) - rmObj.LatestRM = readMarker + rmObj.LatestRM = &dr.ReadMarker if err = rmObj.VerifyMarker(ctx, alloc); err != nil { return nil, common.NewErrorf("download_file", "invalid read marker, "+"failed to verify the read marker: %v", err) } // get file reference - fileref, err := reference.GetReferenceFromLookupHash(ctx, alloc.ID, pathHash) + fileref, err := reference.GetReferenceFromLookupHash(ctx, alloc.ID, dr.PathHash) if err != nil { return nil, common.NewErrorf("download_file", "invalid file path: %v", err) } @@ -264,7 +230,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) (r var shareInfo *reference.ShareInfo if !(isOwner || isCollaborator) { - authTokenString := r.FormValue("auth_token") + authTokenString := dr.AuthToken if authTokenString == "" { return nil, common.NewError("invalid_client", "authticket is required") } @@ -283,11 +249,11 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) (r } // set payer: check for command line payer flag (--rx_pay) - if r.FormValue("rx_pay") == "true" { + if dr.RxPay { payerID = clientID } - readMarker.AuthTicket = datatypes.JSON(authTokenString) + dr.ReadMarker.AuthTicket = datatypes.JSON(authTokenString) // check for file payer flag if fileAttrs, err := fileref.GetAttributes(); err != nil { @@ -315,7 +281,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) (r } } - if latestRM != nil && latestRM.ReadCounter+(numBlocks) != readMarker.ReadCounter { + if latestRM != nil && latestRM.ReadCounter+(dr.NumBlocks) != dr.ReadMarker.ReadCounter { return &blobberhttp.DownloadResponse{ Success: false, LatestRM: latestRM, @@ -325,14 +291,14 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) (r } // check out read pool tokens if read_price > 0 - err = readPreRedeem(ctx, alloc, numBlocks, pendNumBlocks, payerID) + err = readPreRedeem(ctx, alloc, dr.NumBlocks, pendNumBlocks, payerID) if err != nil { return nil, common.NewErrorf("download_file", "pre-redeeming read marker: %v", err) } // reading is allowed var ( - downloadMode = r.FormValue("content") + downloadMode = dr.DownloadMode respData []byte ) if downloadMode == DownloadContentThumb { @@ -342,7 +308,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) (r fileData.Hash = fileref.ThumbnailHash fileData.OnCloud = fileref.OnCloud fileData.ChunkSize = fileref.ChunkSize - respData, err = filestore.GetFileStore().GetFileBlock(alloc.ID, fileData, blockNum, numBlocks) + respData, err = filestore.GetFileStore().GetFileBlock(alloc.ID, fileData, dr.BlockNum, dr.NumBlocks) if err != nil { return nil, common.NewErrorf("download_file", "couldn't get thumbnail block: %v", err) } @@ -354,14 +320,14 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) (r fileData.OnCloud = fileref.OnCloud fileData.ChunkSize = fileref.ChunkSize - respData, err = filestore.GetFileStore().GetFileBlock(alloc.ID, fileData, blockNum, numBlocks) + respData, err = filestore.GetFileStore().GetFileBlock(alloc.ID, fileData, dr.BlockNum, dr.NumBlocks) if err != nil { return nil, common.NewErrorf("download_file", "couldn't get file block: %v", err) } } - readMarker.PayerID = payerID - err = readmarker.SaveLatestReadMarker(ctx, readMarker, latestRM == nil) + dr.ReadMarker.PayerID = payerID + err = readmarker.SaveLatestReadMarker(ctx, &dr.ReadMarker, latestRM == nil) if err != nil { Logger.Error(err.Error()) return nil, common.NewErrorf("download_file", "couldn't save latest read marker") diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler_test.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler_test.go index 71dc038c7..99b09f632 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler_test.go @@ -1,12 +1,10 @@ package handler import ( - "bytes" "context" "database/sql/driver" "encoding/json" "fmt" - "mime/multipart" "net/http" "net/http/httptest" "time" @@ -111,7 +109,7 @@ func TestDownloadFile(t *testing.T) { // reuse code from GOSDK, https://github.com/0chain/gosdk/blob/staging/zboxcore/sdk/blockdownloadworker.go#L150 var addToForm = func( t *testing.T, - formWriter *multipart.Writer, + req *http.Request, p parameters, ) *marker.ReadMarker { rm := &marker.ReadMarker{} @@ -126,14 +124,14 @@ func TestDownloadFile(t *testing.T) { require.NoError(t, err) rmData, err := json.Marshal(rm) require.NoError(t, err) - require.NoError(t, formWriter.WriteField("path_hash", p.inData.pathHash)) - require.NoError(t, formWriter.WriteField("path", p.inData.remotefilepath)) + req.Header.Set("X-Path-Hash", p.inData.pathHash) + req.Header.Set("X-Path", p.inData.remotefilepath) if p.inData.rxPay { - require.NoError(t, formWriter.WriteField("rx_pay", "true")) + req.Header.Set("X-Rxpay", "true") } - require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", p.inData.blockNum))) - require.NoError(t, formWriter.WriteField("num_blocks", fmt.Sprintf("%d", p.inData.numBlocks))) - require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) + req.Header.Set("X-Block-Num", fmt.Sprintf("%d", p.inData.blockNum)) + req.Header.Set("X-Num-Blocks", fmt.Sprintf("%d", p.inData.numBlocks)) + req.Header.Set("X-Read-Marker", string(rmData)) if p.useAuthTicket { authTicket := &marker.AuthTicket{ AllocationID: p.inData.allocationID, @@ -147,12 +145,11 @@ func TestDownloadFile(t *testing.T) { require.NoError(t, authTicket.Sign()) require.NoError(t, client.PopulateClient(mockClientWallet, "bls0chain")) authTicketBytes, _ := json.Marshal(authTicket) - require.NoError(t, formWriter.WriteField("auth_token", string(authTicketBytes))) + req.Header.Set("X-Auth-Token", string(authTicketBytes)) } if len(p.inData.contentMode) > 0 { - require.NoError(t, formWriter.WriteField("content", p.inData.contentMode)) + req.Header.Set("X-Mode", p.inData.contentMode) } - require.NoError(t, formWriter.Close()) return rm } @@ -432,12 +429,8 @@ func TestDownloadFile(t *testing.T) { } setupRequest := func(p parameters) (*http.Request, *marker.ReadMarker) { - body := new(bytes.Buffer) - formWriter := multipart.NewWriter(body) - rm := addToForm(t, formWriter, p) - req := httptest.NewRequest(http.MethodGet, "/v1/file/download/", body) - require.NoError(t, req.ParseForm()) - req.Header.Add("Content-Type", formWriter.FormDataContentType()) + req := httptest.NewRequest(http.MethodGet, "/v1/file/download/", nil) + rm := addToForm(t, req, p) return req, rm } diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index e543f02cc..8c717cae2 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -984,6 +984,11 @@ func getPathHash(r *http.Request, allocationID string) (pathHash, path string, e pathHash = r.FormValue("path_hash") path = r.FormValue("path") + if pathHash == "" && path == "" { + pathHash = r.Header.Get("path_hash") + path = r.Header.Get("path") + } + if pathHash == "" { if path == "" { return "", "", common.NewError("invalid_parameters", "Invalid path")