github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/middleware/auth.go (about)

     1  package middleware
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/md5"
     7  	"fmt"
     8  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
     9  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/oss"
    10  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/upyun"
    11  	"github.com/cloudreve/Cloudreve/v3/pkg/mq"
    12  	"github.com/cloudreve/Cloudreve/v3/pkg/util"
    13  	"github.com/qiniu/go-sdk/v7/auth/qbox"
    14  	"io/ioutil"
    15  	"net/http"
    16  
    17  	model "github.com/cloudreve/Cloudreve/v3/models"
    18  	"github.com/cloudreve/Cloudreve/v3/pkg/auth"
    19  	"github.com/cloudreve/Cloudreve/v3/pkg/cache"
    20  	"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
    21  	"github.com/gin-contrib/sessions"
    22  	"github.com/gin-gonic/gin"
    23  )
    24  
    25  const (
    26  	CallbackFailedStatusCode = http.StatusUnauthorized
    27  )
    28  
    29  // SignRequired 验证请求签名
    30  func SignRequired(authInstance auth.Auth) gin.HandlerFunc {
    31  	return func(c *gin.Context) {
    32  		var err error
    33  		switch c.Request.Method {
    34  		case "PUT", "POST", "PATCH":
    35  			err = auth.CheckRequest(authInstance, c.Request)
    36  		default:
    37  			err = auth.CheckURI(authInstance, c.Request.URL)
    38  		}
    39  
    40  		if err != nil {
    41  			c.JSON(200, serializer.Err(serializer.CodeCredentialInvalid, err.Error(), err))
    42  			c.Abort()
    43  			return
    44  		}
    45  
    46  		c.Next()
    47  	}
    48  }
    49  
    50  // CurrentUser 获取登录用户
    51  func CurrentUser() gin.HandlerFunc {
    52  	return func(c *gin.Context) {
    53  		session := sessions.Default(c)
    54  		uid := session.Get("user_id")
    55  		if uid != nil {
    56  			user, err := model.GetActiveUserByID(uid)
    57  			if err == nil {
    58  				c.Set("user", &user)
    59  			}
    60  		}
    61  		c.Next()
    62  	}
    63  }
    64  
    65  // AuthRequired 需要登录
    66  func AuthRequired() gin.HandlerFunc {
    67  	return func(c *gin.Context) {
    68  		if user, _ := c.Get("user"); user != nil {
    69  			if _, ok := user.(*model.User); ok {
    70  				c.Next()
    71  				return
    72  			}
    73  		}
    74  
    75  		c.JSON(200, serializer.CheckLogin())
    76  		c.Abort()
    77  	}
    78  }
    79  
    80  // WebDAVAuth 验证WebDAV登录及权限
    81  func WebDAVAuth() gin.HandlerFunc {
    82  	return func(c *gin.Context) {
    83  		// OPTIONS 请求不需要鉴权,否则Windows10下无法保存文档
    84  		if c.Request.Method == "OPTIONS" {
    85  			c.Next()
    86  			return
    87  		}
    88  
    89  		username, password, ok := c.Request.BasicAuth()
    90  		if !ok {
    91  			c.Writer.Header()["WWW-Authenticate"] = []string{`Basic realm="cloudreve"`}
    92  			c.Status(http.StatusUnauthorized)
    93  			c.Abort()
    94  			return
    95  		}
    96  
    97  		expectedUser, err := model.GetActiveUserByEmail(username)
    98  		if err != nil {
    99  			c.Status(http.StatusUnauthorized)
   100  			c.Abort()
   101  			return
   102  		}
   103  
   104  		// 密码正确?
   105  		webdav, err := model.GetWebdavByPassword(password, expectedUser.ID)
   106  		if err != nil {
   107  			c.Status(http.StatusUnauthorized)
   108  			c.Abort()
   109  			return
   110  		}
   111  
   112  		// 用户组已启用WebDAV?
   113  		if !expectedUser.Group.WebDAVEnabled {
   114  			c.Status(http.StatusForbidden)
   115  			c.Abort()
   116  			return
   117  		}
   118  
   119  		// 用户组已启用WebDAV代理?
   120  		if !expectedUser.Group.OptionsSerialized.WebDAVProxy {
   121  			webdav.UseProxy = false
   122  		}
   123  
   124  		c.Set("user", &expectedUser)
   125  		c.Set("webdav", webdav)
   126  		c.Next()
   127  	}
   128  }
   129  
   130  // 对上传会话进行验证
   131  func UseUploadSession(policyType string) gin.HandlerFunc {
   132  	return func(c *gin.Context) {
   133  		// 验证key并查找用户
   134  		resp := uploadCallbackCheck(c, policyType)
   135  		if resp.Code != 0 {
   136  			c.JSON(CallbackFailedStatusCode, resp)
   137  			c.Abort()
   138  			return
   139  		}
   140  
   141  		c.Next()
   142  	}
   143  }
   144  
   145  // uploadCallbackCheck 对上传回调请求的 callback key 进行验证,如果成功则返回上传用户
   146  func uploadCallbackCheck(c *gin.Context, policyType string) serializer.Response {
   147  	// 验证 Callback Key
   148  	sessionID := c.Param("sessionID")
   149  	if sessionID == "" {
   150  		return serializer.ParamErr("Session ID cannot be empty", nil)
   151  	}
   152  
   153  	callbackSessionRaw, exist := cache.Get(filesystem.UploadSessionCachePrefix + sessionID)
   154  	if !exist {
   155  		return serializer.Err(serializer.CodeUploadSessionExpired, "上传会话不存在或已过期", nil)
   156  	}
   157  
   158  	callbackSession := callbackSessionRaw.(serializer.UploadSession)
   159  	c.Set(filesystem.UploadSessionCtx, &callbackSession)
   160  	if callbackSession.Policy.Type != policyType {
   161  		return serializer.Err(serializer.CodePolicyNotAllowed, "", nil)
   162  	}
   163  
   164  	// 清理回调会话
   165  	_ = cache.Deletes([]string{sessionID}, filesystem.UploadSessionCachePrefix)
   166  
   167  	// 查找用户
   168  	user, err := model.GetActiveUserByID(callbackSession.UID)
   169  	if err != nil {
   170  		return serializer.Err(serializer.CodeUserNotFound, "", err)
   171  	}
   172  	c.Set(filesystem.UserCtx, &user)
   173  	return serializer.Response{}
   174  }
   175  
   176  // RemoteCallbackAuth 远程回调签名验证
   177  func RemoteCallbackAuth() gin.HandlerFunc {
   178  	return func(c *gin.Context) {
   179  		// 验证签名
   180  		session := c.MustGet(filesystem.UploadSessionCtx).(*serializer.UploadSession)
   181  		authInstance := auth.HMACAuth{SecretKey: []byte(session.Policy.SecretKey)}
   182  		if err := auth.CheckRequest(authInstance, c.Request); err != nil {
   183  			c.JSON(CallbackFailedStatusCode, serializer.Err(serializer.CodeCredentialInvalid, err.Error(), err))
   184  			c.Abort()
   185  			return
   186  		}
   187  
   188  		c.Next()
   189  
   190  	}
   191  }
   192  
   193  // QiniuCallbackAuth 七牛回调签名验证
   194  func QiniuCallbackAuth() gin.HandlerFunc {
   195  	return func(c *gin.Context) {
   196  		session := c.MustGet(filesystem.UploadSessionCtx).(*serializer.UploadSession)
   197  
   198  		// 验证回调是否来自qiniu
   199  		mac := qbox.NewMac(session.Policy.AccessKey, session.Policy.SecretKey)
   200  		ok, err := mac.VerifyCallback(c.Request)
   201  		if err != nil {
   202  			util.Log().Debug("Failed to verify callback request: %s", err)
   203  			c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Failed to verify callback request."})
   204  			c.Abort()
   205  			return
   206  		}
   207  
   208  		if !ok {
   209  			c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Invalid signature."})
   210  			c.Abort()
   211  			return
   212  		}
   213  
   214  		c.Next()
   215  	}
   216  }
   217  
   218  // OSSCallbackAuth 阿里云OSS回调签名验证
   219  func OSSCallbackAuth() gin.HandlerFunc {
   220  	return func(c *gin.Context) {
   221  		err := oss.VerifyCallbackSignature(c.Request)
   222  		if err != nil {
   223  			util.Log().Debug("Failed to verify callback request: %s", err)
   224  			c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Failed to verify callback request."})
   225  			c.Abort()
   226  			return
   227  		}
   228  
   229  		c.Next()
   230  	}
   231  }
   232  
   233  // UpyunCallbackAuth 又拍云回调签名验证
   234  func UpyunCallbackAuth() gin.HandlerFunc {
   235  	return func(c *gin.Context) {
   236  		session := c.MustGet(filesystem.UploadSessionCtx).(*serializer.UploadSession)
   237  
   238  		// 获取请求正文
   239  		body, err := ioutil.ReadAll(c.Request.Body)
   240  		c.Request.Body.Close()
   241  		if err != nil {
   242  			c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: err.Error()})
   243  			c.Abort()
   244  			return
   245  		}
   246  
   247  		c.Request.Body = ioutil.NopCloser(bytes.NewReader(body))
   248  
   249  		// 准备验证Upyun回调签名
   250  		handler := upyun.Driver{Policy: &session.Policy}
   251  		contentMD5 := c.Request.Header.Get("Content-Md5")
   252  		date := c.Request.Header.Get("Date")
   253  		actualSignature := c.Request.Header.Get("Authorization")
   254  
   255  		// 计算正文MD5
   256  		actualContentMD5 := fmt.Sprintf("%x", md5.Sum(body))
   257  		if actualContentMD5 != contentMD5 {
   258  			c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "MD5 mismatch."})
   259  			c.Abort()
   260  			return
   261  		}
   262  
   263  		// 计算理论签名
   264  		signature := handler.Sign(context.Background(), []string{
   265  			"POST",
   266  			c.Request.URL.Path,
   267  			date,
   268  			contentMD5,
   269  		})
   270  
   271  		// 对比签名
   272  		if signature != actualSignature {
   273  			c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Signature not match"})
   274  			c.Abort()
   275  			return
   276  		}
   277  
   278  		c.Next()
   279  	}
   280  }
   281  
   282  // OneDriveCallbackAuth OneDrive回调签名验证
   283  func OneDriveCallbackAuth() gin.HandlerFunc {
   284  	return func(c *gin.Context) {
   285  		// 发送回调结束信号
   286  		mq.GlobalMQ.Publish(c.Param("sessionID"), mq.Message{})
   287  
   288  		c.Next()
   289  	}
   290  }
   291  
   292  // IsAdmin 必须为管理员用户组
   293  func IsAdmin() gin.HandlerFunc {
   294  	return func(c *gin.Context) {
   295  		user, _ := c.Get("user")
   296  		if user.(*model.User).Group.ID != 1 && user.(*model.User).ID != 1 {
   297  			c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, "", nil))
   298  			c.Abort()
   299  			return
   300  		}
   301  
   302  		c.Next()
   303  	}
   304  }