From d28fd4cf9b6d1bcd3350ef19c7759f009fd1ac65 Mon Sep 17 00:00:00 2001 From: murashovven Date: Wed, 7 Apr 2021 15:11:52 +0300 Subject: [PATCH 1/2] Implement signature verification --- .../blobbercore/constants/context_key.go | 11 +- .../0chain.net/blobbercore/datastore/store.go | 4 + code/go/0chain.net/blobbercore/go.mod | 2 + code/go/0chain.net/blobbercore/go.sum | 20 +- .../0chain.net/blobbercore/handler/handler.go | 2 + .../blobbercore/handler/handler_test.go | 1064 +++++++++++++++++ .../handler/object_operation_handler.go | 88 +- .../blobbercore/handler/storage_handler.go | 185 +-- code/go/0chain.net/core/common/handler.go | 15 +- 9 files changed, 1254 insertions(+), 137 deletions(-) create mode 100644 code/go/0chain.net/blobbercore/handler/handler_test.go diff --git a/code/go/0chain.net/blobbercore/constants/context_key.go b/code/go/0chain.net/blobbercore/constants/context_key.go index 4e55a5df0..4f194283f 100644 --- a/code/go/0chain.net/blobbercore/constants/context_key.go +++ b/code/go/0chain.net/blobbercore/constants/context_key.go @@ -2,6 +2,11 @@ package constants import "0chain.net/core/common" -const ALLOCATION_CONTEXT_KEY common.ContextKey = "allocation" -const CLIENT_CONTEXT_KEY common.ContextKey = "client" -const CLIENT_KEY_CONTEXT_KEY common.ContextKey = "client_key" +const ( + ALLOCATION_CONTEXT_KEY common.ContextKey = "allocation" + CLIENT_CONTEXT_KEY common.ContextKey = "client" + CLIENT_KEY_CONTEXT_KEY common.ContextKey = "client_key" + + // CLIENT_SIGNATURE_HEADER_KEY represents key for context value passed with common.ClientSignatureHeader request header. + CLIENT_SIGNATURE_HEADER_KEY common.ContextKey = "signature" +) diff --git a/code/go/0chain.net/blobbercore/datastore/store.go b/code/go/0chain.net/blobbercore/datastore/store.go index 09a19c00f..42aebeca5 100644 --- a/code/go/0chain.net/blobbercore/datastore/store.go +++ b/code/go/0chain.net/blobbercore/datastore/store.go @@ -22,6 +22,10 @@ type Store struct { var store Store +func SetDB(db *gorm.DB) { + store.db = db +} + func GetStore() *Store { return &store } diff --git a/code/go/0chain.net/blobbercore/go.mod b/code/go/0chain.net/blobbercore/go.mod index 127e31178..fba0da3f8 100644 --- a/code/go/0chain.net/blobbercore/go.mod +++ b/code/go/0chain.net/blobbercore/go.mod @@ -8,6 +8,7 @@ require ( 0chain.net/conductor v0.0.0-00010101000000-000000000000 0chain.net/core v0.0.0 github.com/0chain/gosdk v1.1.6 + github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/go-ini/ini v1.55.0 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.6.2 @@ -16,6 +17,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce github.com/spf13/viper v1.7.0 + github.com/stretchr/testify v1.7.0 // indirect go.uber.org/zap v1.15.0 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect diff --git a/code/go/0chain.net/blobbercore/go.sum b/code/go/0chain.net/blobbercore/go.sum index 1e2991713..2747ca58d 100644 --- a/code/go/0chain.net/blobbercore/go.sum +++ b/code/go/0chain.net/blobbercore/go.sum @@ -11,13 +11,14 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/0chain/gosdk v1.0.85 h1:ynXxR45bleZl/qV9u9kJDKTQkwUSwfL9J0fSTuSiDyw= github.com/0chain/gosdk v1.0.85/go.mod h1:edV5GwogiT6nK2+s4QcFCz3saUhkuFK6EIqNJfOt8xc= github.com/0chain/gosdk v1.1.6 h1:urbc9aTp57HAUVgJWP6TsSgRKX61hLBwtSyMV/ngCBo= github.com/0chain/gosdk v1.1.6/go.mod h1:edV5GwogiT6nK2+s4QcFCz3saUhkuFK6EIqNJfOt8xc= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -157,7 +158,6 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.0.2 h1:q1Hsy66zh4vuNsajBUF2PNqfAMMfxU5mk594lPE9vjY= github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.4 h1:RHkX5ZUD9bl/kn0f9dYUWs1N7Nwvo1wwUYvKiR26Zco= github.com/jackc/pgproto3/v2 v2.0.4/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= @@ -217,7 +217,6 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -245,7 +244,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -303,7 +301,6 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -315,6 +312,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -336,18 +335,15 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= @@ -363,7 +359,6 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -403,7 +398,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -430,7 +424,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -444,7 +437,6 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= @@ -480,7 +472,6 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -521,10 +512,11 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/datatypes v0.0.0-20200806042100-bc394008dd0d h1:cEzkQplur9Z++gqjh48MF692Hkdl/jTkbo/7YQ5yssM= gorm.io/datatypes v0.0.0-20200806042100-bc394008dd0d/go.mod h1:n2DTgk9at7cr/CWOTKHWPaflj1fN+yuWpdK4lqSUbWA= gorm.io/driver/mysql v0.3.1 h1:yvUT7Q0I3B9EHJ67NSp6cHbVwcdDHhVUsDAUiFFxRk0= diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 7743d0cca..cde5a7c7a 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -107,6 +107,8 @@ func setupHandlerContext(ctx context.Context, r *http.Request) context.Context { r.Header.Get(common.ClientKeyHeader)) ctx = context.WithValue(ctx, constants.ALLOCATION_CONTEXT_KEY, vars["allocation"]) + // signature is not requered for all requests, but if header is empty it won`t affect anything + ctx = context.WithValue(ctx, constants.CLIENT_SIGNATURE_HEADER_KEY, r.Header.Get(common.ClientSignatureHeader)) return ctx } diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go new file mode 100644 index 000000000..e818bb8c9 --- /dev/null +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -0,0 +1,1064 @@ +package handler + +import ( + "0chain.net/blobbercore/allocation" + bconfig "0chain.net/blobbercore/config" + "0chain.net/blobbercore/datastore" + "0chain.net/blobbercore/filestore" + "0chain.net/blobbercore/reference" + "0chain.net/core/chain" + "0chain.net/core/common" + "0chain.net/core/config" + "0chain.net/core/encryption" + "0chain.net/core/logging" + "bytes" + "encoding/json" + "errors" + "github.com/0chain/gosdk/core/zcncrypto" + "github.com/0chain/gosdk/zcncore" + "github.com/DATA-DOG/go-sqlmock" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "io" + "mime/multipart" + "net/http" + "net/http/httptest" + "os" + "regexp" + "testing" + "time" +) + +func init() { + common.ConfigRateLimits() + chain.SetServerChain(&chain.Chain{}) + config.Configuration.SignatureScheme = "bls0chain" + logging.Logger = zap.NewNop() + + dir, _ := os.Getwd() + filestore.SetupFSStore(dir + "/tmp") + bconfig.Configuration.MaxFileSize = int64(1 << 30) +} + +func setup(t *testing.T) { + // setup wallet + w, err := zcncrypto.NewBLS0ChainScheme().GenerateKeys() + if err != nil { + t.Fatal(err) + } + wBlob, err := json.Marshal(w) + if err != nil { + t.Fatal(err) + } + if err := zcncore.SetWalletInfo(string(wBlob), true); err != nil { + t.Fatal(err) + } + + // setup servers + sharderServ := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + }, + ), + ) + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + n := zcncore.Network{Miners: []string{"miner 1"}, Sharders: []string{sharderServ.URL}} + blob, err := json.Marshal(n) + if err != nil { + t.Fatal(err) + } + + if _, err := w.Write(blob); err != nil { + t.Fatal(err) + } + }, + ), + ) + + if err := zcncore.InitZCNSDK(server.URL, "ed25519"); err != nil { + t.Fatal(err) + } +} + +func makeTestAllocation() *allocation.Allocation { + allocID := "allocation id" + alloc := allocation.Allocation{ + Tx: encryption.Hash("allocation tx"), + ID: allocID, + Terms: []*allocation.Terms{ + { + ID: 1, + AllocationID: allocID, + }, + }, + Expiration: common.Timestamp(time.Now().Add(time.Hour).Unix()), + } + return &alloc +} + +func setupDB(t *testing.T) sqlmock.Sqlmock { + mDB, mock, _ := sqlmock.New() + dialector := postgres.New(postgres.Config{ + DSN: "sqlmock_db_0", + DriverName: "postgres", + Conn: mDB, + PreferSimpleProtocol: true, + }) + db, err := gorm.Open(dialector, &gorm.Config{}) + if err != nil { + t.Fatal(err) + } + datastore.SetDB(db) + + return mock +} + +func setupHandlers(t *testing.T) (*mux.Router, map[string]string) { + router := mux.NewRouter() + + opPath := "/v1/file/objectpath/{allocation}" + opName := "Object_Path" + router.HandleFunc(opPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(ObjectPathHandler), + ), + ), + ).Name(opName) + + rpPath := "/v1/file/referencepath/{allocation}" + rpName := "Reference_Path" + router.HandleFunc(rpPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(ReferencePathHandler), + ), + ), + ).Name(rpName) + + sPath := "/v1/file/stats/{allocation}" + sName := "Stats" + router.HandleFunc(sPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(FileStatsHandler), + ), + ), + ).Name(sName) + + otPath := "/v1/file/objecttree/{allocation}" + otName := "Object_Tree" + router.HandleFunc(otPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(ObjectTreeHandler), + ), + ), + ).Name(otName) + + collPath := "/v1/file/collaborator/{allocation}" + collName := "Collaborator" + router.HandleFunc(collPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(CollaboratorHandler), + ), + ), + ).Name(collName) + + rPath := "/v1/file/rename/{allocation}" + rName := "Rename" + router.HandleFunc(rPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(RenameHandler), + ), + ), + ).Name(rName) + + cPath := "/v1/file/copy/{allocation}" + cName := "Copy" + router.HandleFunc(cPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(CopyHandler), + ), + ), + ).Name(cName) + + aPath := "/v1/file/attributes/{allocation}" + aName := "Attributes" + router.HandleFunc(aPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(UpdateAttributesHandler), + ), + ), + ).Name(aName) + + uPath := "/v1/file/upload/{allocation}" + uName := "Upload" + router.HandleFunc(uPath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(UploadHandler), + ), + ), + ).Name(uName) + + return router, + map[string]string{ + opPath: opName, + rpPath: rpName, + sPath: sName, + otPath: otName, + collPath: collName, + rPath: rName, + cPath: cName, + aPath: aName, + uPath: uName, + } +} + +func isEndpointAllowGetReq(name string) bool { + switch name { + case "Stats", "Rename", "Copy", "Attributes", "Upload": + return false + default: + return true + } +} + +func TestHandlers_Requiring_Signature(t *testing.T) { + setup(t) + + router, handlers := setupHandlers(t) + + sch := zcncrypto.NewBLS0ChainScheme() + _, err := sch.GenerateKeys() + if err != nil { + t.Fatal(err) + } + alloc := makeTestAllocation() + alloc.OwnerPublicKey = sch.GetPublicKey() + alloc.OwnerID = sch.GetPublicKey() + + const ( + path = "/path" + newName = "new name" + connectionID = "connection id" + ) + + type ( + args struct { + w *httptest.ResponseRecorder + r *http.Request + } + test struct { + name string + args args + alloc *allocation.Allocation + setupDbMock func(mock sqlmock.Sqlmock) + wantCode int + wantBody string + } + ) + negativeTests := make([]test, 0) + for _, name := range handlers { + 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: "Object_Path_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/objectpath/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("block_num", "0") + q.Set("path", path) + url.RawQuery = q.Encode() + + 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + 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"}). + 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.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, "/", reference.DIRECTORY, alloc.ID, "/"). + WillReturnRows( + sqlmock.NewRows([]string{"path"}). + AddRow("/"), + ) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + }, + { + name: "Reference_Path_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/referencepath/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("path", path) + url.RawQuery = q.Encode() + + 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + 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"}). + 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.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, path, alloc.ID, "/", "", alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"path"}). + AddRow("/"), + ) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + }, + { + name: "Stats_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/stats/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("path", path) + url.RawQuery = q.Encode() + + r, err := http.NewRequest(http.MethodPost, 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + 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"}). + 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), + ) + + lookUpHash := reference.GetReferenceLookup(alloc.ID, path) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, lookUpHash). + WillReturnRows( + sqlmock.NewRows([]string{"type"}). + AddRow(reference.FILE), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "file_stats"`)). + WillReturnRows( + sqlmock.NewRows([]string{}). + AddRow(), + ) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "write_markers"`)). + WithArgs(sqlmock.AnyArg()). + WillReturnRows( + sqlmock.NewRows([]string{}). + AddRow(), + ) + }, + wantCode: http.StatusOK, + }, + { + name: "Object_Tree_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/objecttree/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("path", path) + url.RawQuery = q.Encode() + + 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + 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"}). + 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.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, path, path+"/%", alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"path"}). + AddRow("/"), + ) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + }, + { + name: "Collaborator_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/collaborator/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("path", path) + q.Set("collab_id", "collab id") + url.RawQuery = q.Encode() + + 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + 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"}). + 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), + ) + + lookUpHash := reference.GetReferenceLookup(alloc.ID, path) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, lookUpHash). + WillReturnRows( + sqlmock.NewRows([]string{"type"}). + AddRow(reference.FILE), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "collaborators"`)). + WillReturnRows( + sqlmock.NewRows([]string{"ref_id"}). + AddRow(0), + ) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + }, + { + name: "Rename_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/rename/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("path", path) + q.Set("new_name", newName) + q.Set("connection_id", connectionID) + url.RawQuery = q.Encode() + + r, err := http.NewRequest(http.MethodPost, 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + 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"}). + 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.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocation_connections" WHERE`)). + WithArgs(connectionID, alloc.ID, alloc.OwnerID, allocation.DeletedConnection). + WillReturnRows( + sqlmock.NewRows([]string{}). + AddRow(), + ) + + lookUpHash := reference.GetReferenceLookup(alloc.ID, path) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, lookUpHash). + WillReturnRows( + sqlmock.NewRows([]string{"type"}). + AddRow(reference.FILE), + ) + + aa := sqlmock.AnyArg() + mock.ExpectExec(`INSERT INTO "allocation_connections"`). + WithArgs(aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "allocation_changes"`)). + WithArgs(aa, aa, aa, aa, aa, aa). + WillReturnRows( + sqlmock.NewRows([]string{}), + ) + }, + wantCode: http.StatusOK, + }, + { + name: "Copy_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/copy/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("path", path) + q.Set("new_name", newName) + q.Set("connection_id", connectionID) + q.Set("dest", "dest") + url.RawQuery = q.Encode() + + r, err := http.NewRequest(http.MethodPost, 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + aa := sqlmock.AnyArg() + + 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.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocation_connections" WHERE`)). + WithArgs(connectionID, alloc.ID, alloc.OwnerID, allocation.DeletedConnection). + WillReturnRows( + sqlmock.NewRows([]string{}). + AddRow(), + ) + + lookUpHash := reference.GetReferenceLookup(alloc.ID, path) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, lookUpHash). + WillReturnRows( + sqlmock.NewRows([]string{"type"}). + AddRow(reference.FILE), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(aa, aa). + WillReturnError(errors.New("")) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(aa, aa). + WillReturnRows( + sqlmock.NewRows([]string{"type"}). + AddRow(reference.DIRECTORY), + ) + + mock.ExpectExec(`INSERT INTO "allocation_connections"`). + WithArgs(aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "allocation_changes"`)). + WithArgs(aa, aa, aa, aa, aa, aa). + WillReturnRows( + sqlmock.NewRows([]string{}), + ) + }, + wantCode: http.StatusOK, + }, + { + name: "Attributes_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/attributes/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + q := url.Query() + q.Set("path", path) + q.Set("new_name", newName) + q.Set("connection_id", connectionID) + + attr := &reference.Attributes{} + attrBytes, err := json.Marshal(attr) + if err != nil { + t.Fatal(err) + } + q.Set("attributes", string(attrBytes)) + url.RawQuery = q.Encode() + + r, err := http.NewRequest(http.MethodPost, 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(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + aa := sqlmock.AnyArg() + + 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.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocation_connections" WHERE`)). + WithArgs(connectionID, alloc.ID, alloc.OwnerID, allocation.DeletedConnection). + WillReturnRows( + sqlmock.NewRows([]string{}). + AddRow(), + ) + + lookUpHash := reference.GetReferenceLookup(alloc.ID, path) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, lookUpHash). + WillReturnRows( + sqlmock.NewRows([]string{"type"}). + AddRow(reference.FILE), + ) + + mock.ExpectExec(`INSERT INTO "allocation_connections"`). + WithArgs(aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "allocation_changes"`)). + WithArgs(aa, aa, aa, aa, aa, aa). + WillReturnRows( + sqlmock.NewRows([]string{}), + ) + }, + wantCode: http.StatusOK, + }, + { + name: "Upload_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/upload/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + q := url.Query() + formFieldByt, err := json.Marshal(&allocation.UpdateFileChange{}) + if err != nil { + t.Fatal(err) + } + q.Set("uploadMeta", string(formFieldByt)) + q.Set("path", path) + q.Set("new_name", newName) + q.Set("connection_id", connectionID) + url.RawQuery = q.Encode() + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + root, _ := os.Getwd() + file, err := os.Open(root + "/handler_test.go") + if err != nil { + t.Fatal(err) + } + fileField, err := formWriter.CreateFormFile("uploadFile", file.Name()) + if err != nil { + t.Fatal(err) + } + fileB, err := io.ReadAll(file) + if err != nil { + t.Fatal(err) + } + if _, err := fileField.Write(fileB); err != nil { + t.Fatal(err) + } + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodPost, url.String(), body) + 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) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + aa := sqlmock.AnyArg() + + 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), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocation_connections" WHERE`)). + WithArgs(connectionID, alloc.ID, alloc.OwnerID, allocation.DeletedConnection). + WillReturnRows( + sqlmock.NewRows([]string{}). + AddRow(), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects"`)). + WithArgs(aa). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectExec(`INSERT INTO "allocation_connections"`). + WithArgs(aa, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "allocation_changes"`)). + WithArgs(aa, aa, aa, aa, aa, aa). + WillReturnRows( + sqlmock.NewRows([]string{}), + ) + }, + wantCode: http.StatusOK, + }, + } + tests := append(positiveTests, negativeTests...) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mock := setupDB(t) + test.setupDbMock(mock) + + router.ServeHTTP(test.args.w, test.args.r) + + assert.Equal(t, test.wantCode, test.args.w.Result().StatusCode) + if test.wantCode != http.StatusOK { + 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/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 4e80e82ce..e34926365 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -208,17 +208,11 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "request_parse_error: %v", err) } - var ( - pathHash = r.FormValue("path_hash") - path = r.FormValue("path") - rxPay = r.FormValue("rx_pay") == "true" - ) + rxPay := r.FormValue("rx_pay") == "true" - if len(pathHash) == 0 { - if len(path) == 0 { - return nil, common.NewError("download_file", "invalid path") - } - pathHash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, common.NewError("download_file", "invalid path") } var blockNumStr = r.FormValue("block_num") @@ -591,14 +585,19 @@ func (fsh *StorageHandler) RenameObject(ctx context.Context, r *http.Request) (i } allocationTx := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := fsh.verifyAllocation(ctx, allocationTx, false) - clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) - _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) + _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) + + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + if len(clientID) == 0 { return nil, common.NewError("invalid_operation", "Invalid client") } @@ -608,14 +607,11 @@ func (fsh *StorageHandler) RenameObject(ctx context.Context, r *http.Request) (i return nil, common.NewError("invalid_parameters", "Invalid name") } - path_hash := r.FormValue("path_hash") - path := r.FormValue("path") - if len(path_hash) == 0 { - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - path_hash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, err } + if len(clientID) == 0 || allocationObj.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } @@ -634,7 +630,7 @@ func (fsh *StorageHandler) RenameObject(ctx context.Context, r *http.Request) (i mutex.Lock() defer mutex.Unlock() - objectRef, err := reference.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + objectRef, err := reference.GetReferenceFromLookupHash(ctx, allocationID, pathHash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) @@ -685,6 +681,11 @@ func (fsh *StorageHandler) UpdateObjectAttributes(ctx context.Context, "Invalid allocation ID passed: %v", err) } + valid, err := verifySignatureFromRequest(r, alloc.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + // runtime type check _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) @@ -705,17 +706,10 @@ func (fsh *StorageHandler) UpdateObjectAttributes(ctx context.Context, "decoding given attributes: %v", err) } - var ( - pathHash = r.FormValue("path_hash") - path = r.FormValue("path") - ) - - if pathHash == "" { - if path == "" { - return nil, common.NewError("update_object_attributes", - "missing path and path_hash") - } - pathHash = reference.GetReferenceLookup(alloc.ID, path) + pathHash, err := pathHashFromReq(r, alloc.ID) + if err != nil { + return nil, common.NewError("update_object_attributes", + "missing path and path_hash") } if alloc.OwnerID != clientID { @@ -779,12 +773,18 @@ func (fsh *StorageHandler) CopyObject(ctx context.Context, r *http.Request) (int } allocationTx := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := fsh.verifyAllocation(ctx, allocationTx, false) - clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) - _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) + _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) + allocationID := allocationObj.ID if len(clientID) == 0 { @@ -796,14 +796,11 @@ func (fsh *StorageHandler) CopyObject(ctx context.Context, r *http.Request) (int return nil, common.NewError("invalid_parameters", "Invalid destination for operation") } - path_hash := r.FormValue("path_hash") - path := r.FormValue("path") - if len(path_hash) == 0 { - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - path_hash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, err } + if len(clientID) == 0 || allocationObj.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } @@ -822,7 +819,7 @@ func (fsh *StorageHandler) CopyObject(ctx context.Context, r *http.Request) (int mutex.Lock() defer mutex.Unlock() - objectRef, err := reference.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + objectRef, err := reference.GetReferenceFromLookupHash(ctx, allocationID, pathHash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) @@ -912,13 +909,18 @@ func (fsh *StorageHandler) WriteFile(ctx context.Context, r *http.Request) (*Upl return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + allocationID := allocationObj.ID if len(clientID) == 0 { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner or the payer of the allocation") } - if err = r.ParseMultipartForm(FORM_FILE_PARSE_MAX_MEMORY); nil != err { + if err := r.ParseMultipartForm(FORM_FILE_PARSE_MAX_MEMORY); err != nil { Logger.Info("Error Parsing the request", zap.Any("error", err)) return nil, common.NewError("request_parse_error", err.Error()) } diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index db1bd97ad..375b03378 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -1,8 +1,10 @@ package handler import ( + "0chain.net/core/encryption" "context" "encoding/json" + "github.com/gorilla/mux" "net/http" "strconv" "strings" @@ -135,16 +137,12 @@ func (fsh *StorageHandler) GetFileMeta(ctx context.Context, r *http.Request) (in _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) - path_hash := r.FormValue("path_hash") - path := r.FormValue("path") - if len(path_hash) == 0 { - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - path_hash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, err } - fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, pathHash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) @@ -208,16 +206,12 @@ func (fsh *StorageHandler) AddCommitMetaTxn(ctx context.Context, r *http.Request _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) - path_hash := r.FormValue("path_hash") - path := r.FormValue("path") - if len(path_hash) == 0 { - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - path_hash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, err } - fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, pathHash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) } @@ -264,20 +258,21 @@ func (fsh *StorageHandler) AddCollaborator(ctx context.Context, r *http.Request) return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + allocationID := allocationObj.ID clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) - path_hash := r.FormValue("path_hash") - path := r.FormValue("path") - if len(path_hash) == 0 { - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - path_hash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, err } - fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, pathHash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) } @@ -344,12 +339,16 @@ func (fsh *StorageHandler) GetFileStats(ctx context.Context, r *http.Request) (i } allocationTx := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := fsh.verifyAllocation(ctx, allocationTx, true) - if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } allocationID := allocationObj.ID + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) if len(clientID) == 0 || allocationObj.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") @@ -357,16 +356,12 @@ func (fsh *StorageHandler) GetFileStats(ctx context.Context, r *http.Request) (i _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) - path_hash := r.FormValue("path_hash") - path := r.FormValue("path") - if len(path_hash) == 0 { - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - path_hash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, err } - fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, pathHash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) @@ -410,18 +405,14 @@ func (fsh *StorageHandler) ListEntities(ctx context.Context, r *http.Request) (* return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } - path_hash := r.FormValue("path_hash") - path := r.FormValue("path") - if len(path_hash) == 0 { - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - path_hash = reference.GetReferenceLookup(allocationID, path) + pathHash, err := pathHashFromReq(r, allocationID) + if err != nil { + return nil, err } - Logger.Info("Path Hash for list dir :" + path_hash) + Logger.Info("Path Hash for list dir :" + pathHash) - fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, pathHash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid path. "+err.Error()) } @@ -480,36 +471,31 @@ func (fsh *StorageHandler) getReferencePath(ctx context.Context, r *http.Request errCh <- common.NewError("invalid_method", "Invalid method used. Use GET instead") return } + allocationTx := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := fsh.verifyAllocation(ctx, allocationTx, false) - if err != nil { errCh <- common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) return } allocationID := allocationObj.ID + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + errCh <- common.NewError("invalid_signature", "Invalid signature") + return + } + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) if len(clientID) == 0 { errCh <- common.NewError("invalid_operation", "Please pass clientID in the header") return } - var paths []string - pathsString := r.FormValue("paths") - if len(pathsString) == 0 { - path := r.FormValue("path") - if len(path) == 0 { - errCh <- common.NewError("invalid_parameters", "Invalid path") - return - } - paths = append(paths, path) - } else { - err = json.Unmarshal([]byte(pathsString), &paths) - if err != nil { - errCh <- common.NewError("invalid_parameters", "Invalid path array json") - return - } + paths, err := pathsFromReq(r) + if err != nil { + errCh <- err + return } rootRef, err := reference.GetReferencePathFromPaths(ctx, allocationID, paths) @@ -561,12 +547,16 @@ func (fsh *StorageHandler) GetObjectPath(ctx context.Context, r *http.Request) ( } allocationTx := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := fsh.verifyAllocation(ctx, allocationTx, false) - if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } allocationID := allocationObj.ID + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) if len(clientID) == 0 || allocationObj.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") @@ -620,6 +610,11 @@ func (fsh *StorageHandler) GetObjectTree(ctx context.Context, r *http.Request) ( } allocationID := allocationObj.ID + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) if len(clientID) == 0 || allocationObj.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") @@ -685,19 +680,9 @@ func (fsh *StorageHandler) CalculateHash(ctx context.Context, r *http.Request) ( return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } - var paths []string - pathsString := r.FormValue("paths") - if len(pathsString) == 0 { - path := r.FormValue("path") - if len(path) == 0 { - return nil, common.NewError("invalid_parameters", "Invalid path") - } - paths = append(paths, path) - } else { - err = json.Unmarshal([]byte(pathsString), &paths) - if err != nil { - return nil, common.NewError("invalid_parameters", "Invalid path array json") - } + paths, err := pathsFromReq(r) + if err != nil { + return nil, err } rootRef, err := reference.GetReferencePathFromPaths(ctx, allocationID, paths) @@ -711,3 +696,59 @@ func (fsh *StorageHandler) CalculateHash(ctx context.Context, r *http.Request) ( result["msg"] = "Hash recalculated for the given paths" return result, nil } + +// verifySignatureFromRequest verifyes signature passed as common.ClientSignatureHeader header. +func verifySignatureFromRequest(r *http.Request, pbK string) (bool, error) { + sign := r.Header.Get(common.ClientSignatureHeader) + if len(sign) < 64 { + return false, nil + } + + vars := mux.Vars(r) + data, ok := vars["allocation"] + if !ok { + return false, common.NewError("invalid_params", "Missing allocation tx") + } + + hash := encryption.Hash(data) + return encryption.Verify(pbK, sign, hash) +} + +// pathsFromReq retrieves paths value from request which can be represented as single "path" value or "paths" values, +// marshalled to json. +func pathsFromReq(r *http.Request) ([]string, error) { + var ( + pathsStr = r.FormValue("paths") + path = r.FormValue("path") + paths = make([]string, 0) + ) + + if len(pathsStr) == 0 { + if len(path) == 0 { + return nil, common.NewError("invalid_parameters", "Invalid path") + } + + return append(paths, path), nil + } + + if err := json.Unmarshal([]byte(pathsStr), &paths); err != nil { + return nil, common.NewError("invalid_parameters", "Invalid path array json") + } + + return paths, nil +} + +func pathHashFromReq(r *http.Request, allocationID string) (string, error) { + var ( + pathHash = r.FormValue("path_hash") + path = r.FormValue("path") + ) + if len(pathHash) == 0 { + if len(path) == 0 { + return "", common.NewError("invalid_parameters", "Invalid path") + } + pathHash = reference.GetReferenceLookup(allocationID, path) + } + + return pathHash, nil +} diff --git a/code/go/0chain.net/core/common/handler.go b/code/go/0chain.net/core/common/handler.go index 486bc845e..2c659dc5c 100644 --- a/code/go/0chain.net/core/common/handler.go +++ b/code/go/0chain.net/core/common/handler.go @@ -10,12 +10,17 @@ import ( "strings" ) -/*AppErrorHeader - a http response header to send an application error code */ -const AppErrorHeader = "X-App-Error-Code" +const ( + // AppErrorHeader - a http response header to send an application error code. + AppErrorHeader = "X-App-Error-Code" -const ClientHeader = "X-App-Client-ID" -const ClientKeyHeader = "X-App-Client-Key" -const TimestampHeader = "X-App-Timestamp" + ClientHeader = "X-App-Client-ID" + ClientKeyHeader = "X-App-Client-Key" + TimestampHeader = "X-App-Timestamp" + + // ClientSignatureHeader represents http request header contains signature. + ClientSignatureHeader = "X-App-Client-Signature" +) /*ReqRespHandlerf - a type for the default hanlder signature */ type ReqRespHandlerf func(w http.ResponseWriter, r *http.Request) From 7453db5e72b56988f9d120f1759f09b4fa4d4ca6 Mon Sep 17 00:00:00 2001 From: murashovven Date: Tue, 20 Apr 2021 16:22:22 +0300 Subject: [PATCH 2/2] Refactore code: - implement MockTheStore method - remove duplicates tests methods - minor fixes --- .../blobbercore/handler/grpc_handler_test.go | 69 +------------------ .../blobbercore/handler/handler_test.go | 51 +++----------- 2 files changed, 11 insertions(+), 109 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/grpc_handler_test.go b/code/go/0chain.net/blobbercore/handler/grpc_handler_test.go index f75fa93ee..98e44443b 100644 --- a/code/go/0chain.net/blobbercore/handler/grpc_handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/grpc_handler_test.go @@ -2,11 +2,8 @@ package handler import ( "context" - "encoding/json" "errors" "net" - "net/http" - "net/http/httptest" "regexp" "testing" "time" @@ -14,15 +11,10 @@ import ( "0chain.net/blobbercore/allocation" "0chain.net/blobbercore/blobbergrpc" "0chain.net/blobbercore/datastore" - "0chain.net/core/chain" "0chain.net/core/common" - "0chain.net/core/logging" - "github.com/0chain/gosdk/core/zcncrypto" - "github.com/0chain/gosdk/zcncore" "github.com/DATA-DOG/go-sqlmock" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" - "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -45,63 +37,6 @@ func startGRPCServer(t *testing.T) { }() } -func initChain(t *testing.T) error { - chain.SetServerChain(&chain.Chain{}) - logging.Logger = zap.NewNop() - - setupWallet() - - sUrl := setupServers(t) - - if err := zcncore.InitZCNSDK(sUrl, "ed25519"); err != nil { - return err - } - - return nil -} - -func setupWallet() error { - w, err := zcncrypto.NewBLS0ChainScheme().GenerateKeys() - if err != nil { - return err - } - wBlob, err := json.Marshal(w) - if err != nil { - return err - } - if err := zcncore.SetWalletInfo(string(wBlob), true); err != nil { - return err - } - - return nil -} - -func setupServers(t *testing.T) (serverUr string) { - sharderServ := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - }, - ), - ) - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - n := zcncore.Network{Miners: []string{"miner 1"}, Sharders: []string{sharderServ.URL}} - blob, err := json.Marshal(n) - if err != nil { - t.Error(err) - } - - if _, err := w.Write(blob); err != nil { - t.Error(err) - } - }, - ), - ) - - return server.URL -} - func makeTestClient() (blobbergrpc.BlobberClient, *grpc.ClientConn, error) { var ( ctx = context.Background() @@ -134,9 +69,7 @@ func makeTestAllocation(exp common.Timestamp) *allocation.Allocation { } func Test_GetAllocation(t *testing.T) { - if err := initChain(t); err != nil { - t.Fatal(err) - } + setup(t) startGRPCServer(t) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index e818bb8c9..0f613734d 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -20,7 +20,6 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "go.uber.org/zap" - "gorm.io/driver/postgres" "gorm.io/gorm" "io" "mime/multipart" @@ -39,7 +38,9 @@ func init() { logging.Logger = zap.NewNop() dir, _ := os.Getwd() - filestore.SetupFSStore(dir + "/tmp") + if _, err := filestore.SetupFSStore(dir + "/tmp"); err != nil { + panic(err) + } bconfig.Configuration.MaxFileSize = int64(1 << 30) } @@ -85,40 +86,7 @@ func setup(t *testing.T) { } } -func makeTestAllocation() *allocation.Allocation { - allocID := "allocation id" - alloc := allocation.Allocation{ - Tx: encryption.Hash("allocation tx"), - ID: allocID, - Terms: []*allocation.Terms{ - { - ID: 1, - AllocationID: allocID, - }, - }, - Expiration: common.Timestamp(time.Now().Add(time.Hour).Unix()), - } - return &alloc -} - -func setupDB(t *testing.T) sqlmock.Sqlmock { - mDB, mock, _ := sqlmock.New() - dialector := postgres.New(postgres.Config{ - DSN: "sqlmock_db_0", - DriverName: "postgres", - Conn: mDB, - PreferSimpleProtocol: true, - }) - db, err := gorm.Open(dialector, &gorm.Config{}) - if err != nil { - t.Fatal(err) - } - datastore.SetDB(db) - - return mock -} - -func setupHandlers(t *testing.T) (*mux.Router, map[string]string) { +func setupHandlers() (*mux.Router, map[string]string) { router := mux.NewRouter() opPath := "/v1/file/objectpath/{allocation}" @@ -228,14 +196,15 @@ func isEndpointAllowGetReq(name string) bool { func TestHandlers_Requiring_Signature(t *testing.T) { setup(t) - router, handlers := setupHandlers(t) + router, handlers := setupHandlers() sch := zcncrypto.NewBLS0ChainScheme() _, err := sch.GenerateKeys() if err != nil { t.Fatal(err) } - alloc := makeTestAllocation() + ts := time.Now().Add(time.Hour) + alloc := makeTestAllocation(common.Timestamp(ts.Unix())) alloc.OwnerPublicKey = sch.GetPublicKey() alloc.OwnerID = sch.GetPublicKey() @@ -961,8 +930,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { if err != nil { t.Fatal(err) } - fileB, err := io.ReadAll(file) - if err != nil { + fileB := make([]byte, 0) + if _, err := io.ReadFull(file, fileB); err != nil { t.Fatal(err) } if _, err := fileField.Write(fileB); err != nil { @@ -1042,7 +1011,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { tests := append(positiveTests, negativeTests...) for _, test := range tests { t.Run(test.name, func(t *testing.T) { - mock := setupDB(t) + mock := datastore.MockTheStore(t) test.setupDbMock(mock) router.ServeHTTP(test.args.w, test.args.r)