Feat: upyun callback & authentication

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

@ -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()
}
}

@ -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,
)

@ -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)
}

@ -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)
}

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

@ -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))
}
}

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

@ -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)

@ -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)
}

Loading…
Cancel
Save