Feat: upyun callback & authentication

pull/247/head
HFO4 5 years ago
parent 21ec3fc710
commit 5befbc21d0

@ -1,14 +1,20 @@
package middleware package middleware
import ( import (
"bytes"
"context"
"crypto/md5"
"fmt"
"github.com/HFO4/cloudreve/models" "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth" "github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/upyun"
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/qiniu/api.v7/v7/auth/qbox" "github.com/qiniu/api.v7/v7/auth/qbox"
"io/ioutil"
"net/http" "net/http"
) )
@ -205,3 +211,58 @@ func OSSCallbackAuth() gin.HandlerFunc {
c.Next() 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()
}
}

@ -140,7 +140,7 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHe
} }
// GetUploadToken 生成新的上传凭证 // 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) credentialTTL := model.GetIntSetting("upload_credential_timeout", 3600)
callBackSessionTTL := model.GetIntSetting("upload_session_timeout", 86400) 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, UID: fs.User.ID,
PolicyID: fs.User.GetPolicyID(), PolicyID: fs.User.GetPolicyID(),
VirtualPath: path, VirtualPath: path,
Name: name,
}, },
callBackSessionTTL, callBackSessionTTL,
) )

@ -180,7 +180,7 @@ func TestFileSystem_GetUploadToken(t *testing.T) {
testHandller := new(FileHeaderMock) testHandller := new(FileHeaderMock)
testHandller.On("Token", testMock.Anything, int64(10), testMock.Anything).Return(serializer.UploadCredential{Token: "test"}, nil) testHandller.On("Token", testMock.Anything, int64(10), testMock.Anything).Return(serializer.UploadCredential{Token: "test"}, nil)
fs.Handler = testHandller fs.Handler = testHandller
res, err := fs.GetUploadToken(ctx, "/", 10) res, err := fs.GetUploadToken(ctx, "/", 10, "123")
testHandller.AssertExpectations(t) testHandller.AssertExpectations(t)
asserts.NoError(err) asserts.NoError(err)
asserts.Equal("test", res.Token) asserts.Equal("test", res.Token)
@ -195,7 +195,7 @@ func TestFileSystem_GetUploadToken(t *testing.T) {
testHandller := new(FileHeaderMock) testHandller := new(FileHeaderMock)
testHandller.On("Token", testMock.Anything, int64(10), testMock.Anything).Return(serializer.UploadCredential{}, errors.New("error")) testHandller.On("Token", testMock.Anything, int64(10), testMock.Anything).Return(serializer.UploadCredential{}, errors.New("error"))
fs.Handler = testHandller fs.Handler = testHandller
_, err := fs.GetUploadToken(ctx, "/", 10) _, err := fs.GetUploadToken(ctx, "/", 10, "123")
testHandller.AssertExpectations(t) testHandller.AssertExpectations(t)
asserts.Error(err) asserts.Error(err)
} }

@ -110,15 +110,21 @@ func (handler Driver) getUploadCredential(ctx context.Context, policy UploadPoli
policyEncoded := base64.StdEncoding.EncodeToString(policyJSON) 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} elements := []string{"POST", "/" + handler.Policy.BucketName, policyEncoded}
value := strings.Join(elements, "&") signStr := handler.Sign(ctx, elements)
mac.Write([]byte(value))
signStr := base64.StdEncoding.EncodeToString((mac.Sum(nil)))
return serializer.UploadCredential{ return serializer.UploadCredential{
Policy: policyEncoded, Policy: policyEncoded,
Token: fmt.Sprintf("UPYUN %s:%s", handler.Policy.AccessKey, signStr), Token: signStr,
}, nil }, 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)
}

@ -29,6 +29,7 @@ type UploadSession struct {
UID uint UID uint
PolicyID uint PolicyID uint
VirtualPath string VirtualPath string
Name string
} }
// UploadCallback 上传回调正文 // UploadCallback 上传回调正文

@ -2,6 +2,7 @@ package controllers
import ( import (
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/HFO4/cloudreve/service/callback" "github.com/HFO4/cloudreve/service/callback"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -45,3 +46,22 @@ func OSSCallback(c *gin.Context) {
c.JSON(200, ErrorResponse(err)) 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))
}
}

@ -144,6 +144,12 @@ func InitMasterRouter() *gin.Engine {
middleware.OSSCallbackAuth(), middleware.OSSCallbackAuth(),
controllers.OSSCallback, controllers.OSSCallback,
) )
// 又拍云策略上传回调
callback.POST(
"upyun/:key",
middleware.UpyunCallbackAuth(),
controllers.UpyunCallback,
)
} }
// 需要登录保护的 // 需要登录保护的

@ -13,7 +13,7 @@ import (
// CallbackProcessService 上传请求回调正文接口 // CallbackProcessService 上传请求回调正文接口
type CallbackProcessService interface { type CallbackProcessService interface {
GetBody() serializer.UploadCallback GetBody(*serializer.UploadSession) serializer.UploadCallback
} }
// RemoteUploadCallbackService 远程存储上传回调请求服务 // RemoteUploadCallbackService 远程存储上传回调请求服务
@ -22,11 +22,11 @@ type RemoteUploadCallbackService struct {
} }
// GetBody 返回回调正文 // GetBody 返回回调正文
func (service RemoteUploadCallbackService) GetBody() serializer.UploadCallback { func (service RemoteUploadCallbackService) GetBody(session *serializer.UploadSession) serializer.UploadCallback {
return service.Data return service.Data
} }
// UploadCallbackService 云存储上传回调请求服务 // UploadCallbackService OOS/七牛云存储上传回调请求服务
type UploadCallbackService struct { type UploadCallbackService struct {
Name string `json:"name"` Name string `json:"name"`
SourceName string `json:"source_name"` SourceName string `json:"source_name"`
@ -34,8 +34,32 @@ type UploadCallbackService struct {
Size uint64 `json:"size"` 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 返回回调正文 // 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{ return serializer.UploadCallback{
Name: service.Name, Name: service.Name,
SourceName: service.SourceName, SourceName: service.SourceName,
@ -46,8 +70,6 @@ func (service UploadCallbackService) GetBody() serializer.UploadCallback {
// ProcessCallback 处理上传结果回调 // ProcessCallback 处理上传结果回调
func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer.Response { func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer.Response {
// 获取回调正文
callbackBody := service.GetBody()
// 创建文件系统 // 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { if err != nil {
@ -62,12 +84,16 @@ func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer.
} }
callbackSession := callbackSessionRaw.(*serializer.UploadSession) callbackSession := callbackSessionRaw.(*serializer.UploadSession)
// 获取回调正文
callbackBody := service.GetBody(callbackSession)
// 重新指向上传策略 // 重新指向上传策略
policy, err := model.GetPolicyByID(callbackSession.PolicyID) policy, err := model.GetPolicyByID(callbackSession.PolicyID)
if err != nil { if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
} }
fs.Policy = &policy fs.Policy = &policy
fs.User.Policy = policy
err = fs.DispatchHandler() err = fs.DispatchHandler()
if err != nil { if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)

@ -12,6 +12,7 @@ import (
type UploadCredentialService struct { type UploadCredentialService struct {
Path string `form:"path" binding:"required"` Path string `form:"path" binding:"required"`
Size uint64 `form:"size" binding:"min=0"` Size uint64 `form:"size" binding:"min=0"`
Name string `form:"name"`
} }
// Get 获取新的上传凭证 // Get 获取新的上传凭证
@ -23,7 +24,7 @@ func (service *UploadCredentialService) Get(ctx context.Context, c *gin.Context)
} }
ctx = context.WithValue(ctx, fsctx.GinCtx, c) 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 { if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err) return serializer.Err(serializer.CodeNotSet, err.Error(), err)
} }

Loading…
Cancel
Save