Feat: upyun callback & authentication
This commit is contained in:
parent
21ec3fc710
commit
5befbc21d0
9 changed files with 138 additions and 16 deletions
|
@ -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…
Add table
Reference in a new issue