Compare commits

...
12 Commits
24 changed files with 188 additions and 46 deletions
+3 -3
View File
@@ -55,7 +55,7 @@
```shell
# 解压程序包
tar - czvf cloudreve_VERSION_OS_ARCH.tar.gz
tar -zxvf cloudreve_VERSION_OS_ARCH.tar.gz
# 赋予执行权限
chmod +x ./cloudreve
@@ -80,7 +80,7 @@ git clone --recurse-submodules https://github.com/cloudreve/Cloudreve.git
```shell
# 进入前端子模块
cd asserts
cd assets
# 安装依赖
yarn install
# 开始构建
@@ -132,4 +132,4 @@ GPL V3
---
> GitHub [@HFO4](https://github.com/HFO4)  · 
> Twitter [@abslant00](https://twitter.com/abslant00)
> Twitter [@abslant00](https://twitter.com/abslant00)
+1 -1
Submodule assets updated: f297f331f9...fdf67a4202
+34
View File
@@ -1,8 +1,12 @@
package bootstrap
import (
"encoding/json"
"fmt"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/request"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/hashicorp/go-version"
)
// InitApplication 初始化应用常量
@@ -18,4 +22,34 @@ func InitApplication() {
================================================
`)
go CheckUpdate()
}
type GitHubRelease struct {
URL string `json:"html_url"`
Name string `json:"name"`
Tag string `json:"tag_name"`
}
// CheckUpdate 检查更新
func CheckUpdate() {
client := request.HTTPClient{}
res, err := client.Request("GET", "https://api.github.com/repos/cloudreve/cloudreve/releases", nil).GetResponse()
if err != nil {
util.Log().Warning("更新检查失败, %s", err)
}
var list []GitHubRelease
if err := json.Unmarshal([]byte(res), &list); err != nil {
util.Log().Warning("更新检查失败, %s", err)
}
if len(list) > 0 {
present, err1 := version.NewVersion(conf.BackendVersion)
latest, err2 := version.NewVersion(list[0].Tag)
if err1 == nil && err2 == nil && latest.GreaterThan(present) {
util.Log().Info("有新的版本 [%s] 可用,下载:%s", list[0].Name, list[0].URL)
}
}
}
+1
View File
@@ -18,6 +18,7 @@ require (
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/go-querystring v1.0.0
github.com/gorilla/websocket v1.4.1
github.com/hashicorp/go-version v1.2.0
github.com/jinzhu/gorm v1.9.11
github.com/juju/ratelimit v1.0.1
github.com/mattn/go-colorable v0.1.4 // indirect
+2
View File
@@ -116,6 +116,8 @@ github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9R
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
+2 -2
View File
@@ -654,7 +654,7 @@ func TestOneDriveCallbackAuth(t *testing.T) {
mock.ExpectQuery("SELECT(.+)users(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1))
mock.ExpectQuery("SELECT(.+)groups(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[522]"))
WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[657]"))
mock.ExpectQuery("SELECT(.+)policies(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123"))
c, _ := gin.CreateTestContext(rec)
@@ -699,7 +699,7 @@ func TestCOSCallbackAuth(t *testing.T) {
mock.ExpectQuery("SELECT(.+)users(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "group_id"}).AddRow(1, 1))
mock.ExpectQuery("SELECT(.+)groups(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[522]"))
WillReturnRows(sqlmock.NewRows([]string{"id", "policies"}).AddRow(1, "[702]"))
mock.ExpectQuery("SELECT(.+)policies(.+)").
WillReturnRows(sqlmock.NewRows([]string{"id", "access_key", "secret_key"}).AddRow(2, "123", "123"))
c, _ := gin.CreateTestContext(rec)
-2
View File
@@ -62,7 +62,6 @@ func TestIsFunctionEnabled(t *testing.T) {
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("POST", "/api/v3/file/dellete/1", nil)
TestFunc(c)
asserts.NoError(mock.ExpectationsWereMet())
asserts.True(c.IsAborted())
}
// 开启
@@ -72,7 +71,6 @@ func TestIsFunctionEnabled(t *testing.T) {
c.Params = []gin.Param{}
c.Request, _ = http.NewRequest("POST", "/api/v3/file/dellete/1", nil)
TestFunc(c)
asserts.NoError(mock.ExpectationsWereMet())
asserts.False(c.IsAborted())
}
+22
View File
@@ -2,6 +2,7 @@ package middleware
import (
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/memstore"
@@ -32,3 +33,24 @@ func Session(secret string) gin.HandlerFunc {
Store.Options(sessions.Options{HttpOnly: true, MaxAge: 7 * 86400, Path: "/"})
return sessions.Sessions("cloudreve-session", Store)
}
// CSRFInit 初始化CSRF标记
func CSRFInit() gin.HandlerFunc {
return func(c *gin.Context) {
util.SetSession(c, map[string]interface{}{"CSRF": true})
c.Next()
}
}
// CSRFCheck 检查CSRF标记
func CSRFCheck() gin.HandlerFunc {
return func(c *gin.Context) {
if check, ok := util.GetSession(c, "CSRF").(bool); ok && check {
c.Next()
return
}
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, "来源非法", nil))
c.Abort()
}
}
+42
View File
@@ -2,8 +2,11 @@ package middleware
import (
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
@@ -21,6 +24,7 @@ func TestSession(t *testing.T) {
asserts.Panics(func() {
Session("2333")
})
conf.RedisConfig.Server = ""
}
}
@@ -28,3 +32,41 @@ func TestSession(t *testing.T) {
func emptyFunc() gin.HandlerFunc {
return func(c *gin.Context) {}
}
func TestCSRFInit(t *testing.T) {
asserts := assert.New(t)
rec := httptest.NewRecorder()
sessionFunc := Session("233")
{
c, _ := gin.CreateTestContext(rec)
c.Request, _ = http.NewRequest("GET", "/test", nil)
sessionFunc(c)
CSRFInit()(c)
asserts.True(util.GetSession(c, "CSRF").(bool))
}
}
func TestCSRFCheck(t *testing.T) {
asserts := assert.New(t)
rec := httptest.NewRecorder()
sessionFunc := Session("233")
// 通过检查
{
c, _ := gin.CreateTestContext(rec)
c.Request, _ = http.NewRequest("GET", "/test", nil)
sessionFunc(c)
CSRFInit()(c)
CSRFCheck()(c)
asserts.False(c.IsAborted())
}
// 未通过检查
{
c, _ := gin.CreateTestContext(rec)
c.Request, _ = http.NewRequest("GET", "/test", nil)
sessionFunc(c)
CSRFCheck()(c)
asserts.True(c.IsAborted())
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ func Init() {
} else {
if conf.DatabaseConfig.Type == "UNSET" {
// 未指定数据库时,使用SQLite
db, err = gorm.Open("sqlite3", util.RelativePath("cloudreve.db"))
db, err = gorm.Open("sqlite3", util.RelativePath(conf.DatabaseConfig.DBFile))
} else {
db, err = gorm.Open(conf.DatabaseConfig.Type, fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local",
conf.DatabaseConfig.User,
+1
View File
@@ -102,6 +102,7 @@ func addDefaultSettings() {
{Name: "onedrive_callback_check", Value: `20`, Type: "timeout"},
{Name: "aria2_call_timeout", Value: `5`, Type: "timeout"},
{Name: "onedrive_chunk_retries", Value: `1`, Type: "retry"},
{Name: "onedrive_source_timeout", Value: `1800`, Type: "timeout"},
{Name: "reset_after_upload_failed", Value: `0`, Type: "upload"},
{Name: "login_captcha", Value: `0`, Type: "login"},
{Name: "reg_captcha", Value: `0`, Type: "login"},
+16 -2
View File
@@ -1,6 +1,7 @@
package model
import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"encoding/json"
@@ -191,11 +192,24 @@ func (user *User) CheckPassword(password string) (bool, error) {
// 根据存储密码拆分为 Salt 和 Digest
passwordStore := strings.Split(user.Password, ":")
if len(passwordStore) != 2 {
if len(passwordStore) != 2 && len(passwordStore) != 3 {
return false, errors.New("Unknown password type")
}
// todo 兼容V2/V1密码
// 兼容V2密码,升级后存储格式为: md5:$HASH:$SALT
if len(passwordStore) == 3 {
if passwordStore[0] != "md5" {
return false, errors.New("Unknown password type")
}
hash := md5.New()
_, err := hash.Write([]byte(passwordStore[2] + password))
bs := hex.EncodeToString(hash.Sum(nil))
if err != nil {
return false, err
}
return bs == passwordStore[1], nil
}
//计算 Salt 和密码组合的SHA1摘要
hash := sha1.New()
_, err := hash.Write([]byte(password + passwordStore[0]))
+21
View File
@@ -144,6 +144,27 @@ func TestUser_CheckPassword(t *testing.T) {
asserts.Error(err)
asserts.False(res)
// 未知密码类型
user = User{}
user.Password = "1:2:3"
res, err = user.CheckPassword("Cause Sega does what nintendon't")
asserts.Error(err)
asserts.False(res)
// V2密码,错误
user = User{}
user.Password = "md5:2:3"
res, err = user.CheckPassword("Cause Sega does what nintendon't")
asserts.NoError(err)
asserts.False(res)
// V2密码,正确
user = User{}
user.Password = "md5:d8446059f8846a2c111a7f53515665fb:sdshare"
res, err = user.CheckPassword("admin")
asserts.NoError(err)
asserts.True(res)
}
func TestNewUser(t *testing.T) {
+1
View File
@@ -14,6 +14,7 @@ type database struct {
Host string
Name string
TablePrefix string
DBFile string
}
// system 系统通用配置
+2 -1
View File
@@ -11,7 +11,8 @@ var RedisConfig = &redis{
// DatabaseConfig 数据库配置
var DatabaseConfig = &database{
Type: "UNSET",
Type: "UNSET",
DBFile: "cloudreve.db",
}
// SystemConfig 系统公用配置
+1 -1
View File
@@ -4,7 +4,7 @@ package conf
var BackendVersion = "3.0.0-beta1"
// RequiredDBVersion 与当前版本匹配的数据库版本
var RequiredDBVersion = "3.0.0-alpha1"
var RequiredDBVersion = "3.0.0-rc1"
// IsPro 是否为Pro版本
var IsPro = "false"
+1 -1
View File
@@ -91,7 +91,7 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err
}
// 尝试删除文件的缩略图(如果有)
_ = os.Remove(value + conf.ThumbConfig.FileSuffix)
_ = os.Remove(util.RelativePath(value + conf.ThumbConfig.FileSuffix))
}
return deleteFailed, retErr
+3 -1
View File
@@ -347,10 +347,12 @@ func (client *Client) makeBatchDeleteRequestsBody(files []string) string {
}
for i, v := range files {
v = strings.TrimPrefix(v, "/")
filePath, _ := url.Parse("/me/drive/root:/")
filePath.Path = path.Join(filePath.Path, v)
req.Requests[i] = BatchRequest{
ID: v,
Method: "DELETE",
URL: "me/drive/root:/" + v,
URL: filePath.EscapedPath(),
}
}
+2 -28
View File
@@ -506,7 +506,7 @@ func TestClient_Upload(t *testing.T) {
asserts.Error(err)
}
// 大文件 分两个分片 成功
// 大文件 分两个分片 reader 返回EOF
{
client.Credential.ExpiresIn = time.Now().Add(time.Duration(100) * time.Hour).Unix()
clientMock := ClientMock{}
@@ -523,24 +523,11 @@ func TestClient_Upload(t *testing.T) {
Body: ioutil.NopCloser(strings.NewReader(`{"uploadUrl":"123321"}`)),
},
})
clientMock.On(
"Request",
"PUT",
"123321",
testMock.Anything,
testMock.Anything,
).Return(&request.Response{
Err: nil,
Response: &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader(`{"uploadUrl":"http://dev.com/2"}`)),
},
})
client.Request = clientMock
err := client.Upload(context.Background(), "123.jpg", 15*1024*1024, strings.NewReader("123"))
clientMock.AssertExpectations(t)
asserts.NoError(err)
asserts.Error(err)
}
// 大文件 分两个分片 失败
@@ -561,19 +548,6 @@ func TestClient_Upload(t *testing.T) {
Body: ioutil.NopCloser(strings.NewReader(`{"uploadUrl":"123321"}`)),
},
})
clientMock.On(
"Request",
"PUT",
"123321",
testMock.Anything,
testMock.Anything,
).Return(&request.Response{
Err: nil,
Response: &http.Response{
StatusCode: 400,
Body: ioutil.NopCloser(strings.NewReader(`{"uploadUrl":"http://dev.com/2"}`)),
},
})
client.Request = clientMock
err := client.Upload(context.Background(), "123.jpg", 15*1024*1024, strings.NewReader("123"))
@@ -129,6 +129,7 @@ func TestDriver_Source(t *testing.T) {
}
handler.Client, _ = NewClient(&model.Policy{})
handler.Client.Credential.ExpiresIn = time.Now().Add(time.Duration(100) * time.Hour).Unix()
cache.Set("setting_onedrive_source_timeout", "1800", 0)
// 失败
{
@@ -137,6 +138,17 @@ func TestDriver_Source(t *testing.T) {
asserts.Empty(res)
}
// 命中缓存 成功
{
handler.Client.Credential.ExpiresIn = time.Now().Add(time.Duration(100) * time.Hour).Unix()
handler.Client.Credential.AccessToken = "1"
cache.Set("onedrive_source_0_123.jpg", "res", 0)
res, err := handler.Source(context.Background(), "123.jpg", url.URL{}, 0, true, 0)
cache.Deletes([]string{"0_123.jpg"}, "onedrive_source_")
asserts.NoError(err)
asserts.Equal("res", res)
}
// 成功
{
handler.Client.Credential.ExpiresIn = time.Now().Add(time.Duration(100) * time.Hour).Unix()
@@ -3,7 +3,9 @@ package onedrive
import (
"context"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/request"
@@ -101,8 +103,20 @@ func (handler Driver) Source(
isDownload bool,
speed int,
) (string, error) {
// 尝试从缓存中查找
if cachedURL, ok := cache.Get(fmt.Sprintf("onedrive_source_%d_%s", handler.Policy.ID, path)); ok {
return cachedURL.(string), nil
}
// 缓存不存在,重新获取
res, err := handler.Client.Meta(ctx, "", path)
if err == nil {
// 写入新的缓存
cache.Set(
fmt.Sprintf("onedrive_source_%d_%s", handler.Policy.ID, path),
res.DownloadURL,
model.GetIntSetting("onedrive_source_timeout", 1800),
)
return res.DownloadURL, nil
}
return "", err
+1 -1
View File
@@ -314,7 +314,7 @@ func AdminGetFile(c *gin.Context) {
res := service.Get(c)
// 是否需要重定向
if res.Code == -301 {
c.Redirect(301, res.Data.(string))
c.Redirect(302, res.Data.(string))
return
}
// 是否有错误发生
+2 -1
View File
@@ -107,7 +107,7 @@ func InitMasterRouter() *gin.Engine {
// 验证码
site.GET("captcha", controllers.Captcha)
// 站点全局配置
site.GET("config", controllers.SiteConfig)
site.GET("config", middleware.CSRFInit(), controllers.SiteConfig)
}
// 用户相关路由
@@ -231,6 +231,7 @@ func InitMasterRouter() *gin.Engine {
)
// 预览分享文件
share.GET("preview/:id",
middleware.CSRFCheck(),
middleware.CheckShareUnlocked(),
middleware.ShareCanPreview(),
middleware.BeforeShareDownload(),
+3 -1
View File
@@ -244,7 +244,9 @@ func (service *SlaveTestService) Test() serializer.Response {
// Add 添加存储策略
func (service *AddPolicyService) Add() serializer.Response {
service.Policy.DirNameRule = strings.TrimPrefix(service.Policy.DirNameRule, "/")
if service.Policy.Type != "local" && service.Policy.Type != "remote" {
service.Policy.DirNameRule = strings.TrimPrefix(service.Policy.DirNameRule, "/")
}
if service.Policy.ID > 0 {
if err := model.DB.Save(&service.Policy).Error; err != nil {