diff --git a/middleware/auth.go b/middleware/auth.go index 2507c20..d91f8d7 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -8,6 +8,7 @@ import ( "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/auth" "github.com/HFO4/cloudreve/pkg/cache" + "github.com/HFO4/cloudreve/pkg/filesystem/oss" "github.com/HFO4/cloudreve/pkg/filesystem/upyun" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" @@ -206,14 +207,19 @@ func OSSCallbackAuth() gin.HandlerFunc { return } - // TODO 验证OSS给出的签名 + err := oss.VerifyCallbackSignature(c.Request) + if err != nil { + util.Log().Debug("回调签名验证失败,%s", err) + c.JSON(401, serializer.QiniuCallbackFailed{Error: "回调签名验证失败"}) + c.Abort() + return + } c.Next() } } // UpyunCallbackAuth 又拍云回调签名验证 -// TODO 测试 func UpyunCallbackAuth() gin.HandlerFunc { return func(c *gin.Context) { // 验证key并查找用户 @@ -226,6 +232,7 @@ func UpyunCallbackAuth() gin.HandlerFunc { // 获取请求正文 body, err := ioutil.ReadAll(c.Request.Body) + c.Request.Body.Close() if err != nil { c.JSON(401, serializer.QiniuCallbackFailed{Error: err.Error()}) c.Abort() diff --git a/middleware/auth_test.go b/middleware/auth_test.go index 196795b..a2e1b02 100644 --- a/middleware/auth_test.go +++ b/middleware/auth_test.go @@ -2,6 +2,7 @@ package middleware import ( "database/sql" + "errors" "github.com/DATA-DOG/go-sqlmock" "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/auth" @@ -12,8 +13,10 @@ import ( "github.com/jinzhu/gorm" "github.com/qiniu/api.v7/v7/auth/qbox" "github.com/stretchr/testify/assert" + "io/ioutil" "net/http" "net/http/httptest" + "strings" "testing" ) @@ -411,7 +414,7 @@ func TestOSSCallbackAuth(t *testing.T) { asserts.True(c.IsAborted()) } - // 成功 + // 签名验证失败 { cache.Set( "callback_testCallBackOSS", @@ -433,14 +436,183 @@ func TestOSSCallbackAuth(t *testing.T) { c.Params = []gin.Param{ {"key", "testCallBackOSS"}, } - c.Request, _ = http.NewRequest("POST", "/api/v3/callback/qiniu/testCallBackOSS", nil) + c.Request, _ = http.NewRequest("POST", "/api/v3/callback/oss/testCallBackOSS", nil) mac := qbox.NewMac("123", "123") token, err := mac.SignRequest(c.Request) asserts.NoError(err) c.Request.Header["Authorization"] = []string{"QBox " + token} AuthFunc(c) asserts.NoError(mock.ExpectationsWereMet()) + asserts.True(c.IsAborted()) + } + + // 成功 + { + cache.Set( + "callback_TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH", + serializer.UploadSession{ + UID: 1, + PolicyID: 2, + VirtualPath: "/", + }, + 0, + ) + cache.Deletes([]string{"1"}, "policy_") + mock.ExpectQuery("SELECT(.+)users(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1)) + mock.ExpectQuery("SELECT(.+)groups(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[2]")) + mock.ExpectQuery("SELECT(.+)policies(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123")) + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{ + {"key", "TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH"}, + } + c.Request, _ = http.NewRequest("POST", "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH", ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`))) + c.Request.Header["Authorization"] = []string{"e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="} + c.Request.Header["X-Oss-Pub-Key-Url"] = []string{"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="} + AuthFunc(c) + asserts.NoError(mock.ExpectationsWereMet()) asserts.False(c.IsAborted()) } } + +type fakeRead string + +func (r fakeRead) Read(p []byte) (int, error) { + return 0, errors.New("error") +} + +func TestUpyunCallbackAuth(t *testing.T) { + asserts := assert.New(t) + rec := httptest.NewRecorder() + AuthFunc := UpyunCallbackAuth() + + // Callback Key 相关验证失败 + { + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{ + {"key", "testUpyunBackRemote"}, + } + c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testUpyunBackRemote", nil) + AuthFunc(c) + asserts.True(c.IsAborted()) + } + + // 无法获取请求正文 + { + cache.Set( + "callback_testCallBackUpyun", + serializer.UploadSession{ + UID: 1, + PolicyID: 2, + VirtualPath: "/", + }, + 0, + ) + cache.Deletes([]string{"1"}, "policy_") + mock.ExpectQuery("SELECT(.+)users(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1)) + mock.ExpectQuery("SELECT(.+)groups(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[2]")) + mock.ExpectQuery("SELECT(.+)policies(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123")) + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{ + {"key", "testCallBackUpyun"}, + } + c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(fakeRead(""))) + AuthFunc(c) + asserts.NoError(mock.ExpectationsWereMet()) + asserts.True(c.IsAborted()) + } + + // 正文MD5不一致 + { + cache.Set( + "callback_testCallBackUpyun", + serializer.UploadSession{ + UID: 1, + PolicyID: 2, + VirtualPath: "/", + }, + 0, + ) + cache.Deletes([]string{"1"}, "policy_") + mock.ExpectQuery("SELECT(.+)users(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1)) + mock.ExpectQuery("SELECT(.+)groups(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[2]")) + mock.ExpectQuery("SELECT(.+)policies(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123")) + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{ + {"key", "testCallBackUpyun"}, + } + c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(strings.NewReader("1"))) + c.Request.Header["Content-Md5"] = []string{"123"} + AuthFunc(c) + asserts.NoError(mock.ExpectationsWereMet()) + asserts.True(c.IsAborted()) + } + + // 签名不一致 + { + cache.Set( + "callback_testCallBackUpyun", + serializer.UploadSession{ + UID: 1, + PolicyID: 2, + VirtualPath: "/", + }, + 0, + ) + cache.Deletes([]string{"1"}, "policy_") + mock.ExpectQuery("SELECT(.+)users(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1)) + mock.ExpectQuery("SELECT(.+)groups(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[2]")) + mock.ExpectQuery("SELECT(.+)policies(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123")) + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{ + {"key", "testCallBackUpyun"}, + } + c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(strings.NewReader("1"))) + c.Request.Header["Content-Md5"] = []string{"c4ca4238a0b923820dcc509a6f75849b"} + AuthFunc(c) + asserts.NoError(mock.ExpectationsWereMet()) + asserts.True(c.IsAborted()) + } + + // 成功 + { + cache.Set( + "callback_testCallBackUpyun", + serializer.UploadSession{ + UID: 1, + PolicyID: 2, + VirtualPath: "/", + }, + 0, + ) + cache.Deletes([]string{"1"}, "policy_") + mock.ExpectQuery("SELECT(.+)users(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1)) + mock.ExpectQuery("SELECT(.+)groups(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[2]")) + mock.ExpectQuery("SELECT(.+)policies(.+)"). + WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123")) + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{ + {"key", "testCallBackUpyun"}, + } + c.Request, _ = http.NewRequest("POST", "/api/v3/callback/upyun/testCallBackUpyun", ioutil.NopCloser(strings.NewReader("1"))) + c.Request.Header["Content-Md5"] = []string{"c4ca4238a0b923820dcc509a6f75849b"} + c.Request.Header["Authorization"] = []string{"UPYUN 123:GWueK9x493BKFFk5gmfdO2Mn6EM="} + AuthFunc(c) + asserts.NoError(mock.ExpectationsWereMet()) + asserts.False(c.IsAborted()) + } +} diff --git a/pkg/filesystem/oss/callback.go b/pkg/filesystem/oss/callback.go new file mode 100644 index 0000000..dbfc4bc --- /dev/null +++ b/pkg/filesystem/oss/callback.go @@ -0,0 +1,116 @@ +package oss + +import ( + "bytes" + "crypto" + "crypto/md5" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "github.com/HFO4/cloudreve/pkg/cache" + "github.com/HFO4/cloudreve/pkg/request" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// GetPublicKey 从回调请求或缓存中获取OSS的回调签名公钥 +func GetPublicKey(r *http.Request) ([]byte, error) { + var pubKey []byte + + // 尝试从缓存中获取 + pub, exist := cache.Get("oss_public_key") + if exist { + return pub.([]byte), nil + } + + // 从请求中获取 + pubURL, err := base64.StdEncoding.DecodeString(r.Header.Get("x-oss-pub-key-url")) + if err != nil { + return pubKey, err + } + + // 确保这个 public key 是由 OSS 颁发的 + if !strings.HasPrefix(string(pubURL), "http://gosspublic.alicdn.com/") && + !strings.HasPrefix(string(pubURL), "https://gosspublic.alicdn.com/") { + return pubKey, errors.New("公钥URL无效") + } + + // 获取公钥 + client := request.HTTPClient{} + body, err := client.Request("GET", string(pubURL), nil). + CheckHTTPResponse(200). + GetResponse() + if err != nil { + return pubKey, err + } + + // 写入缓存 + _ = cache.Set("oss_public_key", []byte(body), 86400*7) + + return []byte(body), nil +} + +func getRequestMD5(r *http.Request) ([]byte, error) { + var byteMD5 []byte + + // 获取请求正文 + body, err := ioutil.ReadAll(r.Body) + r.Body.Close() + if err != nil { + return byteMD5, err + } + r.Body = ioutil.NopCloser(bytes.NewReader(body)) + + strURLPathDecode, err := url.PathUnescape(r.URL.Path) + if err != nil { + return byteMD5, err + } + + strAuth := fmt.Sprintf("%s\n%s", strURLPathDecode, string(body)) + md5Ctx := md5.New() + md5Ctx.Write([]byte(strAuth)) + byteMD5 = md5Ctx.Sum(nil) + + return byteMD5, nil +} + +// VerifyCallbackSignature 验证OSS回调请求 +func VerifyCallbackSignature(r *http.Request) error { + bytePublicKey, err := GetPublicKey(r) + if err != nil { + return err + } + + byteMD5, err := getRequestMD5(r) + if err != nil { + return err + } + + strAuthorizationBase64 := r.Header.Get("authorization") + if strAuthorizationBase64 == "" { + return errors.New("no authorization field in Request header") + } + authorization, _ := base64.StdEncoding.DecodeString(strAuthorizationBase64) + + pubBlock, _ := pem.Decode(bytePublicKey) + if pubBlock == nil { + return errors.New("pubBlock not exist") + } + pubInterface, err := x509.ParsePKIXPublicKey(pubBlock.Bytes) + if (pubInterface == nil) || (err != nil) { + return err + } + pub := pubInterface.(*rsa.PublicKey) + + errorVerifyPKCS1v15 := rsa.VerifyPKCS1v15(pub, crypto.MD5, byteMD5, authorization) + if errorVerifyPKCS1v15 != nil { + return errorVerifyPKCS1v15 + } + + return nil +} diff --git a/pkg/filesystem/oss/callback_test.go b/pkg/filesystem/oss/callback_test.go new file mode 100644 index 0000000..ca82666 --- /dev/null +++ b/pkg/filesystem/oss/callback_test.go @@ -0,0 +1,192 @@ +package oss + +import ( + "github.com/HFO4/cloudreve/pkg/cache" + "github.com/stretchr/testify/assert" + "io/ioutil" + "net/http" + "net/url" + "strings" + "testing" +) + +func TestGetPublicKey(t *testing.T) { + asserts := assert.New(t) + testCases := []struct { + Request http.Request + ResNil bool + Error bool + }{ + // Header解码失败 + { + Request: http.Request{ + Header: http.Header{ + "X-Oss-Pub-Key-Url": {"中文"}, + }, + }, + ResNil: true, + Error: true, + }, + // 公钥URL无效 + { + Request: http.Request{ + Header: http.Header{ + "X-Oss-Pub-Key-Url": {"aHR0cHM6Ly9wb3JuaHViLmNvbQ=="}, + }, + }, + ResNil: true, + Error: true, + }, + // 请求失败 + { + Request: http.Request{ + Header: http.Header{ + "X-Oss-Pub-Key-Url": {"aHR0cDovL2dvc3NwdWJsaWMuYWxpY2RuLmNvbS8yMzQyMzQ="}, + }, + }, + ResNil: true, + Error: true, + }, + // 成功 + { + Request: http.Request{ + Header: http.Header{ + "X-Oss-Pub-Key-Url": {"aHR0cDovL2dvc3NwdWJsaWMuYWxpY2RuLmNvbS9jYWxsYmFja19wdWJfa2V5X3YxLnBlbQ=="}, + }, + }, + ResNil: false, + Error: false, + }, + } + + for i, testCase := range testCases { + asserts.NoError(cache.Deletes([]string{"oss_public_key"}, "")) + res, err := GetPublicKey(&testCase.Request) + if testCase.Error { + asserts.Error(err, "Test Case #%d", i) + } else { + asserts.NoError(err, "Test Case #%d", i) + } + if testCase.ResNil { + asserts.Empty(res, "Test Case #%d", i) + } else { + asserts.NotEmpty(res, "Test Case #%d", i) + } + } + + // 测试缓存 + asserts.NoError(cache.Set("oss_public_key", []byte("123"), 0)) + res, err := GetPublicKey(nil) + asserts.NoError(err) + asserts.Equal([]byte("123"), res) +} + +func TestVerifyCallbackSignature(t *testing.T) { + asserts := assert.New(t) + testPubKey := `-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKs/JBGzwUB2aVht4crBx3oIPBLNsjGs +C0fTXv+nvlmklvkcolvpvXLTjaxUHR3W9LXxQ2EHXAJfCB+6H2YF1k8CAwEAAQ== +-----END PUBLIC KEY----- +` + + // 成功 + { + asserts.NoError(cache.Set("oss_public_key", []byte(testPubKey), 0)) + r := http.Request{ + URL: &url.URL{Path: "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH"}, + Header: map[string][]string{ + "Authorization": {"e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="}, + "X-Oss-Pub-Key-Url": {"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="}, + }, + Body: ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`)), + } + asserts.NoError(VerifyCallbackSignature(&r)) + } + + // 签名错误 + { + asserts.NoError(cache.Set("oss_public_key", []byte(testPubKey), 0)) + r := http.Request{ + URL: &url.URL{Path: "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH"}, + Header: map[string][]string{ + "Authorization": {"e3LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="}, + "X-Oss-Pub-Key-Url": {"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="}, + }, + Body: ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`)), + } + asserts.Error(VerifyCallbackSignature(&r)) + } + + // GetPubKey 失败 + { + asserts.NoError(cache.Deletes([]string{"oss_public_key"}, "")) + r := http.Request{ + URL: &url.URL{Path: "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH"}, + Header: map[string][]string{ + "Authorization": {"e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="}, + }, + Body: ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`)), + } + asserts.Error(VerifyCallbackSignature(&r)) + } + + // getRequestMD5 失败 + { + asserts.NoError(cache.Set("oss_public_key", []byte(testPubKey), 0)) + r := http.Request{ + URL: &url.URL{Path: "%测试"}, + Header: map[string][]string{ + "Authorization": {"e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="}, + "X-Oss-Pub-Key-Url": {"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="}, + }, + Body: ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`)), + } + asserts.Error(VerifyCallbackSignature(&r)) + } + + // 无 Authorization 头 + { + asserts.NoError(cache.Set("oss_public_key", []byte(testPubKey), 0)) + r := http.Request{ + URL: &url.URL{Path: "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH"}, + Header: map[string][]string{ + "X-Oss-Pub-Key-Url": {"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="}, + }, + Body: ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`)), + } + asserts.Error(VerifyCallbackSignature(&r)) + } + + // pub block 不存在 + { + asserts.NoError(cache.Set("oss_public_key", []byte(""), 0)) + r := http.Request{ + URL: &url.URL{Path: "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH"}, + Header: map[string][]string{ + "Authorization": {"e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="}, + "X-Oss-Pub-Key-Url": {"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="}, + }, + Body: ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`)), + } + asserts.Error(VerifyCallbackSignature(&r)) + } + + // ParsePKIXPublicKey出错 + { + asserts.NoError(cache.Set("oss_public_key", []byte("-----BEGIN PUBLIC KEY-----\n-----END PUBLIC KEY-----"), 0)) + r := http.Request{ + URL: &url.URL{Path: "/api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH"}, + Header: map[string][]string{ + "Authorization": {"e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw=="}, + "X-Oss-Pub-Key-Url": {"aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0="}, + }, + Body: ioutil.NopCloser(strings.NewReader(`{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"}`)), + } + asserts.Error(VerifyCallbackSignature(&r)) + } +} + +///api/v3/callback/oss/TnXx5E5VyfJUyM1UdkdDu1rtnJ34EbmH +//{"name":"2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","source_name":"1/1_hFRtDLgM_2f7b2ccf30e9270ea920f1ab8a4037a546a2f0d5.jpg","size":114020,"pic_info":"810,539"} +// aHR0cHM6Ly9nb3NzcHVibGljLmFsaWNkbi5jb20vY2FsbGJhY2tfcHViX2tleV92MS5wZW0= +// e5LwzwTkP9AFAItT4YzvdJOHd0Y0wqTMWhsV/h5SG90JYGAmMd+8LQyj96R+9qUfJWjMt6suuUh7LaOryR87Dw== diff --git a/pkg/request/request.go b/pkg/request/request.go index 9946bf7..a1dfc34 100644 --- a/pkg/request/request.go +++ b/pkg/request/request.go @@ -152,6 +152,8 @@ func (resp *Response) GetResponse() (string, error) { return "", resp.Err } respBody, err := ioutil.ReadAll(resp.Response.Body) + _ = resp.Response.Body.Close() + return string(respBody), err }