From 5befbc21d0b0578557471b74140e948d70e4ac73 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Sat, 18 Jan 2020 10:40:03 +0800 Subject: [PATCH] Feat: upyun callback & authentication --- middleware/auth.go | 61 ++++++++++++++++++++++++++++++++ pkg/filesystem/upload.go | 3 +- pkg/filesystem/upload_test.go | 4 +-- pkg/filesystem/upyun/handller.go | 18 ++++++---- pkg/serializer/upload.go | 1 + routers/controllers/callback.go | 20 +++++++++++ routers/router.go | 6 ++++ service/callback/upload.go | 38 ++++++++++++++++---- service/explorer/upload.go | 3 +- 9 files changed, 138 insertions(+), 16 deletions(-) diff --git a/middleware/auth.go b/middleware/auth.go index bdceac1..2507c20 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -1,14 +1,20 @@ package middleware import ( + "bytes" + "context" + "crypto/md5" + "fmt" "github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/pkg/auth" "github.com/HFO4/cloudreve/pkg/cache" + "github.com/HFO4/cloudreve/pkg/filesystem/upyun" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/qiniu/api.v7/v7/auth/qbox" + "io/ioutil" "net/http" ) @@ -205,3 +211,58 @@ func OSSCallbackAuth() gin.HandlerFunc { c.Next() } } + +// UpyunCallbackAuth 又拍云回调签名验证 +// TODO 测试 +func UpyunCallbackAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 验证key并查找用户 + resp, user := uploadCallbackCheck(c) + if resp.Code != 0 { + c.JSON(401, serializer.QiniuCallbackFailed{Error: resp.Msg}) + c.Abort() + return + } + + // 获取请求正文 + body, err := ioutil.ReadAll(c.Request.Body) + if err != nil { + c.JSON(401, serializer.QiniuCallbackFailed{Error: err.Error()}) + c.Abort() + return + } + + c.Request.Body = ioutil.NopCloser(bytes.NewReader(body)) + + // 准备验证Upyun回调签名 + handler := upyun.Driver{Policy: &user.Policy} + contentMD5 := c.Request.Header.Get("Content-Md5") + date := c.Request.Header.Get("Date") + actualSignature := c.Request.Header.Get("Authorization") + + // 计算正文MD5 + actualContentMD5 := fmt.Sprintf("%x", md5.Sum(body)) + if actualContentMD5 != contentMD5 { + c.JSON(401, serializer.QiniuCallbackFailed{Error: "MD5不一致"}) + c.Abort() + return + } + + // 计算理论签名 + signature := handler.Sign(context.Background(), []string{ + "POST", + c.Request.URL.Path, + date, + contentMD5, + }) + + // 对比签名 + if signature != actualSignature { + c.JSON(401, serializer.QiniuCallbackFailed{Error: "鉴权失败"}) + c.Abort() + return + } + + c.Next() + } +} diff --git a/pkg/filesystem/upload.go b/pkg/filesystem/upload.go index 6eec980..26767c7 100644 --- a/pkg/filesystem/upload.go +++ b/pkg/filesystem/upload.go @@ -140,7 +140,7 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHe } // GetUploadToken 生成新的上传凭证 -func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint64) (*serializer.UploadCredential, error) { +func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint64, name string) (*serializer.UploadCredential, error) { // 获取相关有效期设置 credentialTTL := model.GetIntSetting("upload_credential_timeout", 3600) callBackSessionTTL := model.GetIntSetting("upload_session_timeout", 86400) @@ -167,6 +167,7 @@ func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint UID: fs.User.ID, PolicyID: fs.User.GetPolicyID(), VirtualPath: path, + Name: name, }, callBackSessionTTL, ) diff --git a/pkg/filesystem/upload_test.go b/pkg/filesystem/upload_test.go index 229b41c..e087d78 100644 --- a/pkg/filesystem/upload_test.go +++ b/pkg/filesystem/upload_test.go @@ -180,7 +180,7 @@ func TestFileSystem_GetUploadToken(t *testing.T) { testHandller := new(FileHeaderMock) testHandller.On("Token", testMock.Anything, int64(10), testMock.Anything).Return(serializer.UploadCredential{Token: "test"}, nil) fs.Handler = testHandller - res, err := fs.GetUploadToken(ctx, "/", 10) + res, err := fs.GetUploadToken(ctx, "/", 10, "123") testHandller.AssertExpectations(t) asserts.NoError(err) asserts.Equal("test", res.Token) @@ -195,7 +195,7 @@ func TestFileSystem_GetUploadToken(t *testing.T) { testHandller := new(FileHeaderMock) testHandller.On("Token", testMock.Anything, int64(10), testMock.Anything).Return(serializer.UploadCredential{}, errors.New("error")) fs.Handler = testHandller - _, err := fs.GetUploadToken(ctx, "/", 10) + _, err := fs.GetUploadToken(ctx, "/", 10, "123") testHandller.AssertExpectations(t) asserts.Error(err) } diff --git a/pkg/filesystem/upyun/handller.go b/pkg/filesystem/upyun/handller.go index a948aa5..1046d23 100644 --- a/pkg/filesystem/upyun/handller.go +++ b/pkg/filesystem/upyun/handller.go @@ -110,15 +110,21 @@ func (handler Driver) getUploadCredential(ctx context.Context, policy UploadPoli policyEncoded := base64.StdEncoding.EncodeToString(policyJSON) // 生成签名 - password := fmt.Sprintf("%x", md5.Sum([]byte(handler.Policy.SecretKey))) - mac := hmac.New(sha1.New, []byte(password)) elements := []string{"POST", "/" + handler.Policy.BucketName, policyEncoded} - value := strings.Join(elements, "&") - mac.Write([]byte(value)) - signStr := base64.StdEncoding.EncodeToString((mac.Sum(nil))) + signStr := handler.Sign(ctx, elements) return serializer.UploadCredential{ Policy: policyEncoded, - Token: fmt.Sprintf("UPYUN %s:%s", handler.Policy.AccessKey, signStr), + Token: signStr, }, nil } + +// Sign 计算又拍云的签名头 +func (handler Driver) Sign(ctx context.Context, elements []string) string { + password := fmt.Sprintf("%x", md5.Sum([]byte(handler.Policy.SecretKey))) + mac := hmac.New(sha1.New, []byte(password)) + value := strings.Join(elements, "&") + mac.Write([]byte(value)) + signStr := base64.StdEncoding.EncodeToString((mac.Sum(nil))) + return fmt.Sprintf("UPYUN %s:%s", handler.Policy.AccessKey, signStr) +} diff --git a/pkg/serializer/upload.go b/pkg/serializer/upload.go index 98d6471..8a94aff 100644 --- a/pkg/serializer/upload.go +++ b/pkg/serializer/upload.go @@ -29,6 +29,7 @@ type UploadSession struct { UID uint PolicyID uint VirtualPath string + Name string } // UploadCallback 上传回调正文 diff --git a/routers/controllers/callback.go b/routers/controllers/callback.go index 922c63c..8878b23 100644 --- a/routers/controllers/callback.go +++ b/routers/controllers/callback.go @@ -2,6 +2,7 @@ package controllers import ( "github.com/HFO4/cloudreve/pkg/serializer" + "github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/service/callback" "github.com/gin-gonic/gin" ) @@ -45,3 +46,22 @@ func OSSCallback(c *gin.Context) { c.JSON(200, ErrorResponse(err)) } } + +// UpyunCallback 又拍云上传回调 +func UpyunCallback(c *gin.Context) { + var callbackBody callback.UpyunCallbackService + if err := c.ShouldBind(&callbackBody); err == nil { + if callbackBody.Code != 200 { + util.Log().Debug( + "又拍云回调返回错误代码%d,信息:%s", + callbackBody.Code, + callbackBody.Message, + ) + return + } + res := callback.ProcessCallback(callbackBody, c) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} diff --git a/routers/router.go b/routers/router.go index 699694b..22f89ab 100644 --- a/routers/router.go +++ b/routers/router.go @@ -144,6 +144,12 @@ func InitMasterRouter() *gin.Engine { middleware.OSSCallbackAuth(), controllers.OSSCallback, ) + // 又拍云策略上传回调 + callback.POST( + "upyun/:key", + middleware.UpyunCallbackAuth(), + controllers.UpyunCallback, + ) } // 需要登录保护的 diff --git a/service/callback/upload.go b/service/callback/upload.go index 90dc110..51bdbda 100644 --- a/service/callback/upload.go +++ b/service/callback/upload.go @@ -13,7 +13,7 @@ import ( // CallbackProcessService 上传请求回调正文接口 type CallbackProcessService interface { - GetBody() serializer.UploadCallback + GetBody(*serializer.UploadSession) serializer.UploadCallback } // RemoteUploadCallbackService 远程存储上传回调请求服务 @@ -22,11 +22,11 @@ type RemoteUploadCallbackService struct { } // GetBody 返回回调正文 -func (service RemoteUploadCallbackService) GetBody() serializer.UploadCallback { +func (service RemoteUploadCallbackService) GetBody(session *serializer.UploadSession) serializer.UploadCallback { return service.Data } -// UploadCallbackService 云存储上传回调请求服务 +// UploadCallbackService OOS/七牛云存储上传回调请求服务 type UploadCallbackService struct { Name string `json:"name"` SourceName string `json:"source_name"` @@ -34,8 +34,32 @@ type UploadCallbackService struct { Size uint64 `json:"size"` } +// UpyunCallbackService 又拍云上传回调请求服务 +type UpyunCallbackService struct { + Code int `form:"code" binding:"required"` + Message string `form:"message" binding:"required"` + SourceName string `form:"url" binding:"required"` + Width string `form:"image-width"` + Height string `form:"image-height"` + Size uint64 `form:"file_size"` +} + // GetBody 返回回调正文 -func (service UploadCallbackService) GetBody() serializer.UploadCallback { +func (service UpyunCallbackService) GetBody(session *serializer.UploadSession) serializer.UploadCallback { + res := serializer.UploadCallback{ + Name: session.Name, + SourceName: service.SourceName, + Size: service.Size, + } + if service.Width != "" { + res.PicInfo = service.Width + "," + service.Height + } + + return res +} + +// GetBody 返回回调正文 +func (service UploadCallbackService) GetBody(session *serializer.UploadSession) serializer.UploadCallback { return serializer.UploadCallback{ Name: service.Name, SourceName: service.SourceName, @@ -46,8 +70,6 @@ func (service UploadCallbackService) GetBody() serializer.UploadCallback { // ProcessCallback 处理上传结果回调 func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer.Response { - // 获取回调正文 - callbackBody := service.GetBody() // 创建文件系统 fs, err := filesystem.NewFileSystemFromContext(c) if err != nil { @@ -62,12 +84,16 @@ func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer. } callbackSession := callbackSessionRaw.(*serializer.UploadSession) + // 获取回调正文 + callbackBody := service.GetBody(callbackSession) + // 重新指向上传策略 policy, err := model.GetPolicyByID(callbackSession.PolicyID) if err != nil { return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) } fs.Policy = &policy + fs.User.Policy = policy err = fs.DispatchHandler() if err != nil { return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) diff --git a/service/explorer/upload.go b/service/explorer/upload.go index 5ec15b4..db6fecf 100644 --- a/service/explorer/upload.go +++ b/service/explorer/upload.go @@ -12,6 +12,7 @@ import ( type UploadCredentialService struct { Path string `form:"path" binding:"required"` Size uint64 `form:"size" binding:"min=0"` + Name string `form:"name"` } // Get 获取新的上传凭证 @@ -23,7 +24,7 @@ func (service *UploadCredentialService) Get(ctx context.Context, c *gin.Context) } ctx = context.WithValue(ctx, fsctx.GinCtx, c) - credential, err := fs.GetUploadToken(ctx, service.Path, service.Size) + credential, err := fs.GetUploadToken(ctx, service.Path, service.Size, service.Name) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) }