Test: thumbnail and authn / Modify: read thumbnail config from file

pull/247/head
HFO4 5 years ago
parent f35c585edf
commit c3c0e92964

@ -2,6 +2,11 @@
Debug = true
SessionSecret = 23333
[Thumbnail]
MaxWidth = 400
MaxHeight = 300
FileSuffix = ._thumb
[Database]
Type = mysql
User = root

@ -2,12 +2,9 @@ package model
import (
"crypto/sha1"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/duo-labs/webauthn/webauthn"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
"strings"
@ -59,41 +56,6 @@ type UserOption struct {
PreferredTheme string `json:"preferred_theme"`
}
func (user User) WebAuthnID() []byte {
bs := make([]byte, 8)
binary.LittleEndian.PutUint64(bs, uint64(user.ID))
return bs
}
func (user User) WebAuthnName() string {
return user.Email
}
func (user User) WebAuthnDisplayName() string {
return user.Nick
}
func (user User) WebAuthnIcon() string {
return "https://cdn4.buysellads.net/uu/1/46074/1559075156-slack-carbon-red_2x.png"
}
func (user User) WebAuthnCredentials() []webauthn.Credential {
var res []webauthn.Credential
err := json.Unmarshal([]byte(user.Authn), &res)
if err != nil {
fmt.Println(err)
}
return res
}
func (user *User) RegisterAuthn(credential *webauthn.Credential) {
res, err := json.Marshal([]webauthn.Credential{*credential})
if err != nil {
fmt.Println(err)
}
DB.Model(user).UpdateColumn("authn", string(res))
}
// Root 获取用户的根目录
func (user *User) Root() (*Folder, error) {
var folder Folder
@ -157,18 +119,17 @@ func (user *User) GetPolicyID() uint {
return user.Group.PolicyList[0]
}
return 1
} else {
// 用户指定时,先检查是否为可用策略列表中的值
if util.ContainsUint(user.Group.PolicyList, user.OptionsSerialized.PreferredPolicy) {
return user.OptionsSerialized.PreferredPolicy
}
// 不可用时,返回第一个
if len(user.Group.PolicyList) != 0 {
return user.Group.PolicyList[0]
}
return 1
}
// 用户指定时,先检查是否为可用策略列表中的值
if util.ContainsUint(user.Group.PolicyList, user.OptionsSerialized.PreferredPolicy) {
return user.OptionsSerialized.PreferredPolicy
}
// 不可用时,返回第一个
if len(user.Group.PolicyList) != 0 {
return user.Group.PolicyList[0]
}
return 1
}
// GetUserByID 用ID获取用户

@ -0,0 +1,53 @@
package model
import (
"encoding/binary"
"encoding/json"
"fmt"
"github.com/duo-labs/webauthn/webauthn"
)
/*
`webauthn.User`
*/
// WebAuthnID 返回用户ID
func (user User) WebAuthnID() []byte {
bs := make([]byte, 8)
binary.LittleEndian.PutUint64(bs, uint64(user.ID))
return bs
}
// WebAuthnName 返回用户名
func (user User) WebAuthnName() string {
return user.Email
}
// WebAuthnDisplayName 获得用于展示的用户名
func (user User) WebAuthnDisplayName() string {
return user.Nick
}
// WebAuthnIcon 获得用户头像
func (user User) WebAuthnIcon() string {
return "https://cdn4.buysellads.net/uu/1/46074/1559075156-slack-carbon-red_2x.png"
}
// WebAuthnCredentials 获得已注册的验证器凭证
func (user User) WebAuthnCredentials() []webauthn.Credential {
var res []webauthn.Credential
err := json.Unmarshal([]byte(user.Authn), &res)
if err != nil {
fmt.Println(err)
}
return res
}
// RegisterAuthn 添加新的验证器
func (user *User) RegisterAuthn(credential *webauthn.Credential) {
res, err := json.Marshal([]webauthn.Credential{*credential})
if err != nil {
fmt.Println(err)
}
DB.Model(user).UpdateColumn("authn", string(res))
}

@ -0,0 +1,84 @@
package model
import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/duo-labs/webauthn/webauthn"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"testing"
)
func TestUser_RegisterAuthn(t *testing.T) {
asserts := assert.New(t)
credential := webauthn.Credential{}
user := User{
Model: gorm.Model{ID: 1},
}
{
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)").
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
user.RegisterAuthn(&credential)
asserts.NoError(mock.ExpectationsWereMet())
}
}
func TestUser_WebAuthnCredentials(t *testing.T) {
asserts := assert.New(t)
user := User{
Model: gorm.Model{ID: 1},
Authn: `[{"ID":"123","PublicKey":"+4sg1vYcjg/+=","AttestationType":"packed","Authenticator":{"AAGUID":"+lg==","SignCount":0,"CloneWarning":false}}]`,
}
{
credentials := user.WebAuthnCredentials()
asserts.Len(credentials, 1)
}
}
func TestUser_WebAuthnDisplayName(t *testing.T) {
asserts := assert.New(t)
user := User{
Model: gorm.Model{ID: 1},
Nick: "123",
}
{
nick := user.WebAuthnDisplayName()
asserts.Equal("123", nick)
}
}
func TestUser_WebAuthnIcon(t *testing.T) {
asserts := assert.New(t)
user := User{
Model: gorm.Model{ID: 1},
}
{
icon := user.WebAuthnIcon()
asserts.NotEmpty(icon)
}
}
func TestUser_WebAuthnID(t *testing.T) {
asserts := assert.New(t)
user := User{
Model: gorm.Model{ID: 1},
}
{
id := user.WebAuthnID()
asserts.Len(id, 8)
}
}
func TestUser_WebAuthnName(t *testing.T) {
asserts := assert.New(t)
user := User{
Model: gorm.Model{ID: 1},
Email: "abslant@foxmail.com",
}
{
name := user.WebAuthnName()
asserts.Equal("abslant@foxmail.com", name)
}
}

@ -5,11 +5,11 @@ import (
"github.com/duo-labs/webauthn/webauthn"
)
var Authn *webauthn.WebAuthn
var AuthnInstance *webauthn.WebAuthn
func Init() {
var err error
Authn, err = webauthn.New(&webauthn.Config{
AuthnInstance, err = webauthn.New(&webauthn.Config{
RPDisplayName: "Duo Labs", // Display Name for your site
RPID: "localhost", // Generally the FQDN for your site
RPOrigin: "http://localhost:3000", // The origin URL for WebAuthn requests

@ -0,0 +1,15 @@
package authn
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestInit(t *testing.T) {
asserts := assert.New(t)
asserts.NotPanics(func() {
Init()
})
asserts.NotNil(AuthnInstance)
}

@ -3,7 +3,6 @@ package conf
import (
"github.com/HFO4/cloudreve/pkg/util"
"github.com/go-ini/ini"
"github.com/mojocn/base64Captcha"
"gopkg.in/go-playground/validator.v8"
)
@ -45,36 +44,11 @@ type redis struct {
DB string
}
// RedisConfig Redis服务器配置
var RedisConfig = &redis{
Server: "",
Password: "",
DB: "0",
}
// DatabaseConfig 数据库配置
var DatabaseConfig = &database{
Type: "UNSET",
}
// SystemConfig 系统公用配置
var SystemConfig = &system{
Debug: false,
}
// CaptchaConfig 验证码配置
var CaptchaConfig = &captcha{
Height: 60,
Width: 240,
Mode: 3,
ComplexOfNoiseText: base64Captcha.CaptchaComplexLower,
ComplexOfNoiseDot: base64Captcha.CaptchaComplexLower,
IsShowHollowLine: false,
IsShowNoiseDot: false,
IsShowNoiseText: false,
IsShowSlimeLine: false,
IsShowSineLine: false,
CaptchaLen: 6,
// 缩略图 配置
type thumb struct {
MaxWidth uint
MaxHeight uint
FileSuffix string `validate:"min=1"`
}
var cfg *ini.File
@ -90,10 +64,11 @@ func Init(path string) {
}
sections := map[string]interface{}{
"Database": DatabaseConfig,
"System": SystemConfig,
"Captcha": CaptchaConfig,
"Redis": RedisConfig,
"Database": DatabaseConfig,
"System": SystemConfig,
"Captcha": CaptchaConfig,
"Redis": RedisConfig,
"Thumbnail": ThumbConfig,
}
for sectionName, sectionStruct := range sections {
err = mapSection(sectionName, sectionStruct)

@ -0,0 +1,41 @@
package conf
import "github.com/mojocn/base64Captcha"
// RedisConfig Redis服务器配置
var RedisConfig = &redis{
Server: "",
Password: "",
DB: "0",
}
// DatabaseConfig 数据库配置
var DatabaseConfig = &database{
Type: "UNSET",
}
// SystemConfig 系统公用配置
var SystemConfig = &system{
Debug: false,
}
// CaptchaConfig 验证码配置
var CaptchaConfig = &captcha{
Height: 60,
Width: 240,
Mode: 3,
ComplexOfNoiseText: base64Captcha.CaptchaComplexLower,
ComplexOfNoiseDot: base64Captcha.CaptchaComplexLower,
IsShowHollowLine: false,
IsShowNoiseDot: false,
IsShowNoiseText: false,
IsShowSlimeLine: false,
IsShowSineLine: false,
CaptchaLen: 6,
}
var ThumbConfig = &thumb{
MaxWidth: 400,
MaxHeight: 300,
FileSuffix: "._thumb",
}

@ -4,6 +4,7 @@ import (
"context"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/thumb"
"github.com/HFO4/cloudreve/pkg/util"
@ -54,7 +55,7 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
image, err := thumb.NewThumbFromFile(source, file.Name)
if err != nil {
util.Log().Warning("生成缩略图时无法解析[%s]图像数据:%s", file.SourceName, err)
util.Log().Warning("生成缩略图时无法解析 [%s] 图像数据:%s", file.SourceName, err)
return
}
@ -64,9 +65,9 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
// 生成缩略图
image.GetThumb(fs.GenerateThumbnailSize(w, h))
// 保存到文件
err = image.Save(file.SourceName + "._thumb")
err = image.Save(file.SourceName + conf.ThumbConfig.FileSuffix)
if err != nil {
util.Log().Warning("无法保存缩略图:%s", file.SourceName, err)
util.Log().Warning("无法保存缩略图:%s", err)
return
}
@ -75,12 +76,12 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
// 失败时删除缩略图文件
if err != nil {
_, _ = fs.Handler.Delete(newCtx, []string{file.SourceName + "._thumb"})
_, _ = fs.Handler.Delete(newCtx, []string{file.SourceName + conf.ThumbConfig.FileSuffix})
}
}
// GenerateThumbnailSize 获取要生成的缩略图的尺寸
// TODO 从配置文件读取
// TODO 优先从数据库中获得
func (fs *FileSystem) GenerateThumbnailSize(w, h int) (uint, uint) {
return 400, 300
return conf.ThumbConfig.MaxWidth, conf.ThumbConfig.MaxHeight
}

@ -0,0 +1,156 @@
package filesystem
import (
"context"
"errors"
"fmt"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"image"
"image/jpeg"
"os"
"testing"
)
func CreateTestImage() *os.File {
file, err := os.Create("TestFileSystem_GenerateThumbnail.jpeg")
alpha := image.NewAlpha(image.Rect(0, 0, 500, 200))
jpeg.Encode(file, alpha, nil)
if err != nil {
fmt.Println(err)
}
_, _ = file.Seek(0, 0)
return file
}
func TestFileSystem_GetThumb(t *testing.T) {
asserts := assert.New(t)
fs := FileSystem{
User: &model.User{
Model: gorm.Model{ID: 1},
},
}
ctx := context.Background()
// 正常
{
testHandler := new(FileHeaderMock)
testHandler.On("Thumb", testMock.Anything, "123.jpg").Return(&response.ContentResponse{URL: "123"}, nil)
fs.Handler = testHandler
mock.ExpectQuery("SELECT(.+)").
WithArgs(10, 1).
WillReturnRows(
sqlmock.NewRows(
[]string{"id", "pic_info", "source_name"}).
AddRow(10, "10,10", "123.jpg"),
)
res, err := fs.GetThumb(ctx, 10)
asserts.NoError(mock.ExpectationsWereMet())
testHandler.AssertExpectations(t)
asserts.NoError(err)
asserts.Equal("123", res.URL)
}
// 文件不存在
{
mock.ExpectQuery("SELECT(.+)").
WithArgs(10, 1).
WillReturnRows(
sqlmock.NewRows(
[]string{"id", "pic_info", "source_name"}),
)
_, err := fs.GetThumb(ctx, 10)
asserts.NoError(mock.ExpectationsWereMet())
asserts.Error(err)
}
}
func TestFileSystem_GenerateThumbnail(t *testing.T) {
asserts := assert.New(t)
fs := FileSystem{
User: &model.User{
Model: gorm.Model{ID: 1},
},
}
ctx := context.Background()
// 成功
{
src := CreateTestImage()
testHandler := new(FileHeaderMock)
testHandler.On("Get", testMock.Anything, "TestFileSystem_GenerateThumbnail.jpeg").Return(src, nil)
fs.Handler = testHandler
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
file := &model.File{
Name: "123.jpg",
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
}
fs.GenerateThumbnail(ctx, file)
asserts.NoError(mock.ExpectationsWereMet())
testHandler.AssertExpectations(t)
asserts.True(util.Exists("TestFileSystem_GenerateThumbnail.jpeg" + conf.ThumbConfig.FileSuffix))
}
// 更新信息失败后删除文件
{
src := CreateTestImage()
testHandler := new(FileHeaderMock)
testHandler.On("Get", testMock.Anything, "TestFileSystem_GenerateThumbnail.jpeg").Return(src, nil)
testHandler.On("Delete", testMock.Anything, testMock.Anything).Return([]string{}, nil)
fs.Handler = testHandler
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)").WillReturnError(errors.New("error"))
mock.ExpectRollback()
file := &model.File{
Name: "123.jpg",
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
}
fs.GenerateThumbnail(ctx, file)
asserts.NoError(mock.ExpectationsWereMet())
testHandler.AssertExpectations(t)
}
// 不能生成缩略图
{
file := &model.File{
Name: "123.123",
SourceName: "TestFileSystem_GenerateThumbnail.jpeg",
}
fs.GenerateThumbnail(ctx, file)
asserts.NoError(mock.ExpectationsWereMet())
}
}
func TestFileSystem_GenerateThumbnailSize(t *testing.T) {
asserts := assert.New(t)
fs := FileSystem{
User: &model.User{
Model: gorm.Model{ID: 1},
},
}
asserts.NotPanics(func() {
_, _ = fs.GenerateThumbnailSize(0, 0)
})
}

@ -2,6 +2,7 @@ package local
import (
"context"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/util"
"io"
@ -78,7 +79,7 @@ func (handler Handler) Delete(ctx context.Context, files []string) ([]string, er
}
// 尝试删除文件的缩略图(如果有)
_ = os.Remove(value + "._thumb")
_ = os.Remove(value + conf.ThumbConfig.FileSuffix)
}
return deleteFailed, retErr
@ -86,7 +87,7 @@ func (handler Handler) Delete(ctx context.Context, files []string) ([]string, er
// Thumb 获取文件缩略图
func (handler Handler) Thumb(ctx context.Context, path string) (*response.ContentResponse, error) {
file, err := handler.Get(ctx, path+"._thumb")
file, err := handler.Get(ctx, path+conf.ThumbConfig.FileSuffix)
if err != nil {
return nil, err
}

@ -0,0 +1,101 @@
package thumb
import (
"fmt"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/stretchr/testify/assert"
"image"
"image/jpeg"
"os"
"testing"
)
func CreateTestImage() *os.File {
file, err := os.Create("TestNewThumbFromFile.jpeg")
alpha := image.NewAlpha(image.Rect(0, 0, 500, 200))
jpeg.Encode(file, alpha, nil)
if err != nil {
fmt.Println(err)
}
_, _ = file.Seek(0, 0)
return file
}
func TestNewThumbFromFile(t *testing.T) {
asserts := assert.New(t)
file := CreateTestImage()
defer file.Close()
// 无扩展名时
{
thumb, err := NewThumbFromFile(file, "123")
asserts.Error(err)
asserts.Nil(thumb)
}
{
thumb, err := NewThumbFromFile(file, "123.jpg")
asserts.NoError(err)
asserts.NotNil(thumb)
}
{
thumb, err := NewThumbFromFile(file, "123.jpeg")
asserts.Error(err)
asserts.Nil(thumb)
}
{
thumb, err := NewThumbFromFile(file, "123.png")
asserts.Error(err)
asserts.Nil(thumb)
}
{
thumb, err := NewThumbFromFile(file, "123.gif")
asserts.Error(err)
asserts.Nil(thumb)
}
{
thumb, err := NewThumbFromFile(file, "123.3211")
asserts.Error(err)
asserts.Nil(thumb)
}
}
func TestThumb_GetSize(t *testing.T) {
asserts := assert.New(t)
file := CreateTestImage()
defer file.Close()
thumb, err := NewThumbFromFile(file, "123.jpg")
asserts.NoError(err)
w, h := thumb.GetSize()
asserts.Equal(500, w)
asserts.Equal(200, h)
}
func TestThumb_GetThumb(t *testing.T) {
asserts := assert.New(t)
file := CreateTestImage()
defer file.Close()
thumb, err := NewThumbFromFile(file, "123.jpg")
asserts.NoError(err)
asserts.NotPanics(func() {
thumb.GetThumb(10, 10)
})
}
func TestThumb_Save(t *testing.T) {
asserts := assert.New(t)
file := CreateTestImage()
defer file.Close()
thumb, err := NewThumbFromFile(file, "123.jpg")
asserts.NoError(err)
err = thumb.Save("/:noteexist/")
asserts.Error(err)
err = thumb.Save("TestThumb_Save.png")
asserts.NoError(err)
asserts.True(util.Exists("TestThumb_Save.png"))
}

@ -20,7 +20,7 @@ func StartLoginAuthn(c *gin.Context) {
return
}
options, sessionData, err := authn.Authn.BeginLogin(expectedUser)
options, sessionData, err := authn.AuthnInstance.BeginLogin(expectedUser)
if err != nil {
c.JSON(200, ErrorResponse(err))
return
@ -52,7 +52,7 @@ func FinishLoginAuthn(c *gin.Context) {
var sessionData webauthn.SessionData
err = json.Unmarshal(sessionDataJSON, &sessionData)
_, err = authn.Authn.FinishLogin(expectedUser, sessionData, c.Request)
_, err = authn.AuthnInstance.FinishLogin(expectedUser, sessionData, c.Request)
if err != nil {
c.JSON(200, serializer.Err(401, "用户邮箱或密码错误", err))
@ -68,7 +68,7 @@ func FinishLoginAuthn(c *gin.Context) {
// StartRegAuthn 开始注册WebAuthn信息
func StartRegAuthn(c *gin.Context) {
currUser := CurrentUser(c)
options, sessionData, err := authn.Authn.BeginRegistration(currUser)
options, sessionData, err := authn.AuthnInstance.BeginRegistration(currUser)
if err != nil {
c.JSON(200, ErrorResponse(err))
return
@ -94,7 +94,7 @@ func FinishRegAuthn(c *gin.Context) {
var sessionData webauthn.SessionData
err := json.Unmarshal(sessionDataJSON, &sessionData)
credential, err := authn.Authn.FinishRegistration(currUser, sessionData, c.Request)
credential, err := authn.AuthnInstance.FinishRegistration(currUser, sessionData, c.Request)
currUser.RegisterAuthn(credential)
if err != nil {

Loading…
Cancel
Save