Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions code/go/0chain.net/blobbercore/handler/authticket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package handler

import (
"context"
"encoding/json"
"fmt"
"regexp"

"github.com/0chain/blobber/code/go/0chain.net/blobbercore/allocation"
"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"
"gorm.io/gorm"
)

// verifyAuthTicket verifies authTicket and returns authToken and error if any. For any error authToken is nil
func verifyAuthTicket(ctx context.Context, db *gorm.DB, authTokenString string, allocationObj *allocation.Allocation, refRequested *reference.Ref, clientID string) (*readmarker.AuthTicket, error) {
if authTokenString == "" {
return nil, common.NewError("invalid_parameters", "Auth ticket is required")
}

authToken := &readmarker.AuthTicket{}
if err := json.Unmarshal([]byte(authTokenString), &authToken); err != nil {
return nil, common.NewError("invalid_parameters", "Error parsing the auth ticket for download."+err.Error())
}

if err := authToken.Verify(allocationObj, clientID); err != nil {
return nil, err
}

if refRequested.LookupHash != authToken.FilePathHash {
authTokenRef, err := reference.GetLimitedRefFieldsByLookupHashWith(ctx, db, authToken.AllocationID, authToken.FilePathHash, []string{"id", "path"})
if err != nil {
return nil, err
}

if matched, _ := regexp.MatchString(fmt.Sprintf("^%v", authTokenRef.Path), refRequested.Path); !matched {
return nil, common.NewError("invalid_parameters", "Auth ticket is not valid for the resource being requested")
}
}

return authToken, nil
}
18 changes: 9 additions & 9 deletions code/go/0chain.net/blobbercore/handler/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ type Context struct {
ClientID string
// ClientKey client wallet public key
ClientKey string
// AllocationTx optional. allcation id in request
AllocationTx string
// AllocationId optional. allocation id in request
AllocationId string
// Signature optional. signature in request
Signature string

Expand Down Expand Up @@ -146,7 +146,7 @@ func WithHandler(handler func(ctx *Context) (interface{}, error)) func(w http.Re

w.Header().Set("Content-Type", "application/json")

ctx, err := WithAuth(r)
ctx, err := WithVerify(r)
statusCode := ctx.StatusCode

if err != nil {
Expand Down Expand Up @@ -182,8 +182,8 @@ func WithHandler(handler func(ctx *Context) (interface{}, error)) func(w http.Re
}
}

// WithAuth verify alloation and signature
func WithAuth(r *http.Request) (*Context, error) {
// WithVerify verify allocation and signature
func WithVerify(r *http.Request) (*Context, error) {

ctx := &Context{
Context: context.TODO(),
Expand All @@ -198,11 +198,11 @@ func WithAuth(r *http.Request) (*Context, error) {

ctx.ClientID = r.Header.Get(common.ClientHeader)
ctx.ClientKey = r.Header.Get(common.ClientKeyHeader)
ctx.AllocationTx = ctx.Vars["allocation"]
ctx.AllocationId = ctx.Vars["allocation"]
ctx.Signature = r.Header.Get(common.ClientSignatureHeader)

if len(ctx.AllocationTx) > 0 {
alloc, err := allocation.GetOrCreate(ctx, ctx.Store, ctx.AllocationTx)
if len(ctx.AllocationId) > 0 {
alloc, err := allocation.GetOrCreate(ctx, ctx.Store, ctx.AllocationId)

if err != nil {
if errors.Is(common.ErrBadRequest, err) {
Expand All @@ -223,7 +223,7 @@ func WithAuth(r *http.Request) (*Context, error) {
if alloc.OwnerID != ctx.ClientID {
publicKey = ctx.ClientKey
}
valid, err := verifySignatureFromRequest(ctx.AllocationTx, ctx.Signature, publicKey)
valid, err := verifySignatureFromRequest(ctx.AllocationId, ctx.Signature, publicKey)

if !valid {
ctx.StatusCode = http.StatusBadRequest
Expand Down
2 changes: 2 additions & 0 deletions code/go/0chain.net/blobbercore/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ func SetupHandlers(r *mux.Router) {
r.HandleFunc("/v1/writemarker/lock/{allocation}/{connection}", WithHandler(UnlockWriteMarker)).Methods(http.MethodDelete, http.MethodOptions)

r.HandleFunc("/v1/hashnode/root/{allocation}", WithHandler(LoadRootHashnode)).Methods(http.MethodGet, http.MethodOptions)
r.HandleFunc("/v1/playlist/latest/{allocation}", WithHandler(LoadPlaylist)).Methods(http.MethodGet, http.MethodOptions)
r.HandleFunc("/v1/playlist/file/{allocation}", WithHandler(LoadPlaylistFile)).Methods(http.MethodGet, http.MethodOptions)
}

func WithReadOnlyConnection(handler common.JSONResponderF) common.JSONResponderF {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
// LoadRootHashnode load root node with its descendant nodes
func LoadRootHashnode(ctx *Context) (interface{}, error) {

root, err := reference.LoadRootHashnode(ctx, ctx.AllocationTx)
root, err := reference.LoadRootHashnode(ctx, ctx.AllocationId)
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ FROM reference_objects`).

rr := httptest.NewRecorder()
handler := http.HandlerFunc(WithHandler(func(ctx *Context) (interface{}, error) {
ctx.AllocationTx = "allocation_handler_load_root"
ctx.AllocationId = "allocation_handler_load_root"
return LoadRootHashnode(ctx)
}))

Expand Down
87 changes: 87 additions & 0 deletions code/go/0chain.net/blobbercore/handler/handler_playlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package handler

import (
"errors"

"github.com/0chain/blobber/code/go/0chain.net/blobbercore/reference"
"github.com/0chain/blobber/code/go/0chain.net/core/common"
)

// LoadPlaylist load latest playlist
func LoadPlaylist(ctx *Context) (interface{}, error) {
q := ctx.Request.URL.Query()

since := q.Get("since")

authTokenString := q.Get("auth_token")

//load playlist with auth ticket
if len(authTokenString) > 0 {

lookupHash := q.Get("lookup_hash")

if len(lookupHash) == 0 {
return nil, errors.New("lookup_hash_missed: auth_token and lookup_hash are required")
}

fileRef, err := reference.GetLimitedRefFieldsByLookupHash(ctx, ctx.AllocationId, lookupHash, []string{"id", "path", "lookup_hash", "type", "name"})
if err != nil {
return nil, common.NewError("invalid_lookup_hash", err.Error())
}

authToken, err := verifyAuthTicket(ctx, ctx.Store.GetDB(), authTokenString, ctx.Allocation, fileRef, ctx.ClientID)
if err != nil {
return nil, err
}
if authToken == nil {
return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket.")
}

return reference.LoadPlaylist(ctx, ctx.AllocationId, "", lookupHash, since)

}

if ctx.ClientID == "" || ctx.ClientID != ctx.Allocation.OwnerID {
return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation")
}

return reference.LoadPlaylist(ctx, ctx.AllocationId, q.Get("path"), "", since)
}

// LoadPlaylistFile load playlist file
func LoadPlaylistFile(ctx *Context) (interface{}, error) {
q := ctx.Request.URL.Query()

lookupHash := q.Get("lookup_hash")
if len(lookupHash) == 0 {
return nil, errors.New("lookup_hash_missed: lookup_hash is required")
}

authTokenString := q.Get("auth_token")

//load playlist with auth ticket
if len(authTokenString) > 0 {

fileRef, err := reference.GetLimitedRefFieldsByLookupHash(ctx, ctx.AllocationId, lookupHash, []string{"id", "path", "lookup_hash", "type", "name"})
if err != nil {
return nil, common.NewError("invalid_lookup_hash", err.Error())
}

authToken, err := verifyAuthTicket(ctx, ctx.Store.GetDB(), authTokenString, ctx.Allocation, fileRef, ctx.ClientID)
if err != nil {
return nil, err
}
if authToken == nil {
return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket.")
}

return reference.LoadPlaylistFile(ctx, ctx.AllocationId, lookupHash)

}

if ctx.ClientID == "" || ctx.ClientID != ctx.Allocation.OwnerID {
return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation")
}

return reference.LoadPlaylistFile(ctx, ctx.AllocationId, lookupHash)
}
155 changes: 155 additions & 0 deletions code/go/0chain.net/blobbercore/handler/handler_playlist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package handler

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"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/reference"
"github.com/gorilla/mux"
gomocket "github.com/selvatico/go-mocket"
"github.com/stretchr/testify/require"
)

func TestPlaylist_LoadPlaylist(t *testing.T) {

datastore.UseMocket(true)

gomocket.Catcher.NewMock().
WithQuery(`SELECT "lookup_hash","name","path","num_of_blocks","parent_path","size","mimetype","type" FROM "reference_objects" WHERE allocation_id = $1 and parent_path = $2 and type='f' and name like '%!!(string=path)t(string=)s`).
WithArgs("AllocationId", "path").
WithReply([]map[string]interface{}{
{
"lookup_hash": "lookup_hash1",
"name": "name1",
"path": "path1",
"num_of_blocks": 1,
"parent_path": "parent_path1",
"size": 10,
"mimetype": "mimetype1",
"type": "f",
},
{
"lookup_hash": "lookup_hash2",
"name": "name2",
"path": "path2",
"num_of_blocks": 2,
"parent_path": "parent_path2",
"size": 20,
"mimetype": "mimetype2",
"type": "f",
},
})

r := mux.NewRouter()
SetupHandlers(r)

req, err := http.NewRequest(http.MethodGet, "/v1/playlist/latest/{allocation}?path=path", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
handler := http.HandlerFunc(WithHandler(func(ctx *Context) (interface{}, error) {
ctx.AllocationId = "AllocationId"
ctx.ClientID = "ownerid"
ctx.Allocation = &allocation.Allocation{
ID: "AllocationId",
OwnerID: "ownerid",
}
return LoadPlaylist(ctx)
}))

handler.ServeHTTP(rr, req)

require.Equal(t, http.StatusOK, rr.Code)

var files []reference.PlaylistFile

err = json.Unmarshal(rr.Body.Bytes(), &files)
require.Nil(t, err)

require.NotNil(t, files)
require.Len(t, files, 2)

require.Equal(t, files[0].LookupHash, "lookup_hash1")
require.Equal(t, files[0].Name, "name1")
require.Equal(t, files[0].Path, "path1")
require.Equal(t, files[0].NumBlocks, int64(1))
require.Equal(t, files[0].ParentPath, "parent_path1")
require.Equal(t, files[0].Size, int64(10))
require.Equal(t, files[0].MimeType, "mimetype1")
require.Equal(t, files[0].Type, "f")

require.Equal(t, files[1].LookupHash, "lookup_hash2")
require.Equal(t, files[1].Name, "name2")
require.Equal(t, files[1].Path, "path2")
require.Equal(t, files[1].NumBlocks, int64(2))
require.Equal(t, files[1].ParentPath, "parent_path2")
require.Equal(t, files[1].Size, int64(20))
require.Equal(t, files[1].MimeType, "mimetype2")
require.Equal(t, files[1].Type, "f")
}

func TestPlaylist_LoadPlaylistFile(t *testing.T) {

datastore.UseMocket(true)

gomocket.Catcher.NewMock().
WithQuery(`SELECT "lookup_hash","name","path","num_of_blocks","parent_path","size","mimetype","type" FROM "reference_objects" WHERE allocation_id = $1 and lookup_hash = $2 ORDER BY "reference_objects"."lookup_hash" LIMIT 1`).
WithArgs("AllocationId", "lookup_hash").
WithReply([]map[string]interface{}{
{
"lookup_hash": "lookup_hash",
"name": "name",
"path": "path",
"num_of_blocks": 1,
"parent_path": "parent_path",
"size": 10,
"mimetype": "mimetype",
"type": "f",
},
})

r := mux.NewRouter()
SetupHandlers(r)

req, err := http.NewRequest(http.MethodGet, "/v1/playlist/file/{allocation}?lookup_hash=lookup_hash", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
handler := http.HandlerFunc(WithHandler(func(ctx *Context) (interface{}, error) {
ctx.AllocationId = "AllocationId"
ctx.ClientID = "ownerid"
ctx.Allocation = &allocation.Allocation{
ID: "AllocationId",
OwnerID: "ownerid",
}
return LoadPlaylistFile(ctx)
}))

handler.ServeHTTP(rr, req)

require.Equal(t, http.StatusOK, rr.Code)

file := &reference.PlaylistFile{}

err = json.Unmarshal(rr.Body.Bytes(), file)
require.Nil(t, err)

require.NotNil(t, file)

require.Equal(t, file.LookupHash, "lookup_hash")
require.Equal(t, file.Name, "name")
require.Equal(t, file.Path, "path")
require.Equal(t, file.NumBlocks, int64(1))
require.Equal(t, file.ParentPath, "parent_path")
require.Equal(t, file.Size, int64(10))
require.Equal(t, file.MimeType, "mimetype")
require.Equal(t, file.Type, "f")
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func LockWriteMarker(ctx *Context) (interface{}, error) {
connectionID, _ := ctx.FormValue("connection_id")
requestTime, _ := ctx.FormTime("request_time")

result, err := WriteMarkerMutext.Lock(ctx, ctx.AllocationTx, connectionID, requestTime)
result, err := WriteMarkerMutext.Lock(ctx, ctx.AllocationId, connectionID, requestTime)
if err != nil {
return nil, err
}
Expand All @@ -23,7 +23,7 @@ func LockWriteMarker(ctx *Context) (interface{}, error) {
func UnlockWriteMarker(ctx *Context) (interface{}, error) {
connectionID := ctx.Vars["connection"]

err := WriteMarkerMutext.Unlock(ctx, ctx.AllocationTx, connectionID)
err := WriteMarkerMutext.Unlock(ctx, ctx.AllocationId, connectionID)
if err != nil {
return nil, err
}
Expand Down
Loading