mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
b23e67ae6a
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
9514 lines
307 KiB
Go
9514 lines
307 KiB
Go
// Copyright (C) 2019 Nicola Murino
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, version 3.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package common_test
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"math"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/mhale/smtpd"
|
|
"github.com/minio/sio"
|
|
"github.com/pkg/sftp"
|
|
"github.com/pquerna/otp"
|
|
"github.com/pquerna/otp/totp"
|
|
"github.com/rs/xid"
|
|
"github.com/rs/zerolog"
|
|
"github.com/sftpgo/sdk"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/studio-b12/gowebdav"
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"github.com/drakkan/sftpgo/v2/internal/common"
|
|
"github.com/drakkan/sftpgo/v2/internal/config"
|
|
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
|
"github.com/drakkan/sftpgo/v2/internal/httpclient"
|
|
"github.com/drakkan/sftpgo/v2/internal/httpdtest"
|
|
"github.com/drakkan/sftpgo/v2/internal/kms"
|
|
"github.com/drakkan/sftpgo/v2/internal/logger"
|
|
"github.com/drakkan/sftpgo/v2/internal/mfa"
|
|
"github.com/drakkan/sftpgo/v2/internal/sftpd"
|
|
"github.com/drakkan/sftpgo/v2/internal/smtp"
|
|
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
"github.com/drakkan/sftpgo/v2/internal/vfs"
|
|
"github.com/drakkan/sftpgo/v2/internal/webdavd"
|
|
)
|
|
|
|
const (
|
|
httpAddr = "127.0.0.1:9999"
|
|
httpProxyAddr = "127.0.0.1:7777"
|
|
sftpServerAddr = "127.0.0.1:4022"
|
|
smtpServerAddr = "127.0.0.1:2525"
|
|
webDavServerPort = 9191
|
|
httpFsPort = 34567
|
|
defaultUsername = "test_common_sftp"
|
|
defaultPassword = "test_password"
|
|
defaultSFTPUsername = "test_common_sftpfs_user"
|
|
defaultHTTPFsUsername = "httpfs_ftp_user"
|
|
httpFsWellKnowDir = "/wellknow"
|
|
osWindows = "windows"
|
|
testFileName = "test_file_common_sftp.dat"
|
|
testDir = "test_dir_common"
|
|
testPubKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1"
|
|
testPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
|
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
|
|
NhAAAAAwEAAQAAAYEAtN449A/nY5O6cSH/9Doa8a3ISU0WZJaHydTaCLuO+dkqtNpnV5mq
|
|
zFbKidXAI1eSwVctw9ReVOl1uK6aZF3lbXdOD8W9PXobR9KUUT2qBx5QC4ibfAqDKWymDA
|
|
PG9ylzz64hsYBqJr7VNk9kTFEUsDmWzLabLoH42Elnp8mF/lTkWIcpVp0ly/etS08gttXo
|
|
XenekJ1vRuxOYWDCEzGPU7kGc920TmM14k7IDdPoOh5+3sRUKedKeOUrVDH1f0n7QjHQsZ
|
|
cbshp8tgqzf734zu8cTqNrr+6taptdEOOij1iUL/qYGfzny/hA48tO5+UFUih5W8ftp0+E
|
|
NBIDkkGgk2MJ92I7QAXyMVsIABXco+mJT7pQi9tqlODGIQ3AOj0gcA3X/Ib8QX77Ih3TPi
|
|
XEh77/P1XiYZOgpp2cRmNH8QbqaL9u898hDvJwIPJPuj2lIltTElH7hjBf5LQfCzrLV7BD
|
|
10rM7sl4jr+A2q8jl1Ikp+25kainBBZSbrDummT9AAAFgDU/VLk1P1S5AAAAB3NzaC1yc2
|
|
EAAAGBALTeOPQP52OTunEh//Q6GvGtyElNFmSWh8nU2gi7jvnZKrTaZ1eZqsxWyonVwCNX
|
|
ksFXLcPUXlTpdbiummRd5W13Tg/FvT16G0fSlFE9qgceUAuIm3wKgylspgwDxvcpc8+uIb
|
|
GAaia+1TZPZExRFLA5lsy2my6B+NhJZ6fJhf5U5FiHKVadJcv3rUtPILbV6F3p3pCdb0bs
|
|
TmFgwhMxj1O5BnPdtE5jNeJOyA3T6Doeft7EVCnnSnjlK1Qx9X9J+0Ix0LGXG7IafLYKs3
|
|
+9+M7vHE6ja6/urWqbXRDjoo9YlC/6mBn858v4QOPLTuflBVIoeVvH7adPhDQSA5JBoJNj
|
|
CfdiO0AF8jFbCAAV3KPpiU+6UIvbapTgxiENwDo9IHAN1/yG/EF++yId0z4lxIe+/z9V4m
|
|
GToKadnEZjR/EG6mi/bvPfIQ7ycCDyT7o9pSJbUxJR+4YwX+S0Hws6y1ewQ9dKzO7JeI6/
|
|
gNqvI5dSJKftuZGopwQWUm6w7ppk/QAAAAMBAAEAAAGAHKnC+Nq0XtGAkIFE4N18e6SAwy
|
|
0WSWaZqmCzFQM0S2AhJnweOIG/0ZZHjsRzKKauOTmppQk40dgVsejpytIek9R+aH172gxJ
|
|
2n4Cx0UwduRU5x8FFQlNc/kl722B0JWfJuB/snOZXv6LJ4o5aObIkozt2w9tVFeAqjYn2S
|
|
1UsNOfRHBXGsTYwpRDwFWP56nKo2d2wBBTHDhCy6fb2dLW1fvSi/YspueOGIlHpvlYKi2/
|
|
CWqvs9xVrwcScMtiDoQYq0khhO0efLCxvg/o+W9CLMVM2ms4G1zoSUQKN0oYWWQJyW4+VI
|
|
YneWO8UpN0J3ElXKi7bhgAat7dBaM1g9IrAzk153DiEFZNsPxGOgL/+YdQN7zUBx/z7EkI
|
|
jyv80RV7fpUXvcq2p+qNl6UVig3VSzRrnsaJkUWu/A0u59ha7ocv6NxDIXjxpIDJme16GF
|
|
quiGVBQNnYJymS/vFEbGf6bgf7iRmMCRUMG4nqLA6fPYP9uAtch+CmDfVLZC/fIdC5AAAA
|
|
wQCDissV4zH6bfqgxJSuYNk8Vbb+19cF3b7gH1rVlB3zxpCAgcRgMHC+dP1z2NRx7UW9MR
|
|
nye6kjpkzZZ0OigLqo7TtEq8uTglD9o6W7mRXqhy5A/ySOmqPL3ernHHQhGuoNODYAHkOU
|
|
u2Rh8HXi+VLwKZcLInPOYJvcuLG4DxN8WfeVvlMHwhAOaTNNOtL4XZDHQeIPc4qHmJymmv
|
|
sV7GuyQ6yW5C10uoGdxRPd90Bh4z4h2bKfZFjvEBbSBVkqrlAAAADBAN/zNtNayd/dX7Cr
|
|
Nb4sZuzCh+CW4BH8GOePZWNCATwBbNXBVb5cR+dmuTqYm+Ekz0VxVQRA1TvKncluJOQpoa
|
|
Xj8r0xdIgqkehnfDPMKtYVor06B9Fl1jrXtXU0Vrr6QcBWruSVyK1ZxqcmcNK/+KolVepe
|
|
A6vcl/iKaG4U7su166nxLST06M2EgcSVsFJHpKn5+WAXC+X0Gx8kNjWIIb3GpiChdc0xZD
|
|
mq02xZthVJrTCVw/e7gfDoB2QRsNV8HwAAAMEAzsCghZVp+0YsYg9oOrw4tEqcbEXEMhwY
|
|
0jW8JNL8Spr1Ibp5Dw6bRSk5azARjmJtnMJhJ3oeHfF0eoISqcNuQXGndGQbVM9YzzAzc1
|
|
NbbCNsVroqKlChT5wyPNGS+phi2bPARBno7WSDvshTZ7dAVEP2c9MJW0XwoSevwKlhgSdt
|
|
RLFFQ/5nclJSdzPBOmQouC0OBcMFSrYtMeknJ4VvueVvve5HcHFaEsaMc7ABAGaLYaBQOm
|
|
iixITGvaNZh/tjAAAACW5pY29sYUBwMQE=
|
|
-----END OPENSSH PRIVATE KEY-----`
|
|
)
|
|
|
|
var (
|
|
configDir = filepath.Join(".", "..", "..")
|
|
allPerms = []string{dataprovider.PermAny}
|
|
homeBasePath string
|
|
logFilePath string
|
|
backupsPath string
|
|
testFileContent = []byte("test data")
|
|
lastReceivedEmail receivedEmail
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
homeBasePath = os.TempDir()
|
|
logFilePath = filepath.Join(configDir, "common_test.log")
|
|
backupsPath = filepath.Join(os.TempDir(), "backups")
|
|
logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel)
|
|
|
|
os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1")
|
|
os.Setenv("SFTPGO_COMMON__ALLOW_SELF_CONNECTIONS", "1")
|
|
os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin")
|
|
os.Setenv("SFTPGO_DEFAULT_ADMIN_PASSWORD", "password")
|
|
err := config.LoadConfig(configDir, "")
|
|
if err != nil {
|
|
logger.ErrorToConsole("error loading configuration: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
providerConf := config.GetProviderConf()
|
|
providerConf.BackupsPath = backupsPath
|
|
logger.InfoToConsole("Starting COMMON tests, provider: %v", providerConf.Driver)
|
|
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
if err != nil {
|
|
logger.ErrorToConsole("error initializing data provider: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = common.Initialize(config.GetCommonConfig(), 0)
|
|
if err != nil {
|
|
logger.WarnToConsole("error initializing common: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
httpConfig := config.GetHTTPConfig()
|
|
httpConfig.Timeout = 5
|
|
httpConfig.RetryMax = 0
|
|
httpConfig.Initialize(configDir) //nolint:errcheck
|
|
kmsConfig := config.GetKMSConfig()
|
|
err = kmsConfig.Initialize()
|
|
if err != nil {
|
|
logger.ErrorToConsole("error initializing kms: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
mfaConfig := config.GetMFAConfig()
|
|
err = mfaConfig.Initialize()
|
|
if err != nil {
|
|
logger.ErrorToConsole("error initializing MFA: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
sftpdConf := config.GetSFTPDConfig()
|
|
sftpdConf.Bindings[0].Port = 4022
|
|
sftpdConf.EnabledSSHCommands = []string{"*"}
|
|
sftpdConf.Bindings = append(sftpdConf.Bindings, sftpd.Binding{
|
|
Port: 4024,
|
|
})
|
|
sftpdConf.KeyboardInteractiveAuthentication = true
|
|
|
|
httpdConf := config.GetHTTPDConfig()
|
|
httpdConf.Bindings[0].Port = 4080
|
|
httpdtest.SetBaseURL("http://127.0.0.1:4080")
|
|
|
|
webDavConf := config.GetWebDAVDConfig()
|
|
webDavConf.Bindings = []webdavd.Binding{
|
|
{
|
|
Port: webDavServerPort,
|
|
},
|
|
}
|
|
|
|
go func() {
|
|
if err := sftpdConf.Initialize(configDir); err != nil {
|
|
logger.ErrorToConsole("could not start SFTP server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
if err := httpdConf.Initialize(configDir, 0); err != nil {
|
|
logger.ErrorToConsole("could not start HTTP server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
if err := webDavConf.Initialize(configDir); err != nil {
|
|
logger.ErrorToConsole("could not start WebDAV server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
|
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
|
waitTCPListening(webDavConf.Bindings[0].GetAddress())
|
|
startHTTPFs()
|
|
|
|
go func() {
|
|
// start a test HTTP server to receive action notifications
|
|
http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
|
|
fmt.Fprintf(w, "OK\n")
|
|
})
|
|
http.HandleFunc("/404", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
fmt.Fprintf(w, "Not found\n")
|
|
})
|
|
http.HandleFunc("/multipart", func(w http.ResponseWriter, r *http.Request) {
|
|
err := r.ParseMultipartForm(1048576)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprintf(w, "KO\n")
|
|
return
|
|
}
|
|
defer r.MultipartForm.RemoveAll() //nolint:errcheck
|
|
fmt.Fprintf(w, "OK\n")
|
|
})
|
|
if err := http.ListenAndServe(httpAddr, nil); err != nil {
|
|
logger.ErrorToConsole("could not start HTTP notification server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
common.Config.ProxyProtocol = 2
|
|
listener, err := net.Listen("tcp", httpProxyAddr)
|
|
if err != nil {
|
|
logger.ErrorToConsole("error creating listener for proxy protocol server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
proxyListener, err := common.Config.GetProxyListener(listener)
|
|
if err != nil {
|
|
logger.ErrorToConsole("error creating proxy protocol listener: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
common.Config.ProxyProtocol = 0
|
|
|
|
s := &http.Server{}
|
|
if err := s.Serve(proxyListener); err != nil {
|
|
logger.ErrorToConsole("could not start HTTP proxy protocol server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
if err := smtpd.ListenAndServe(smtpServerAddr, func(_ net.Addr, from string, to []string, data []byte) error {
|
|
lastReceivedEmail.set(from, to, data)
|
|
return nil
|
|
}, "SFTPGo test", "localhost"); err != nil {
|
|
logger.ErrorToConsole("could not start SMTP server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
waitTCPListening(httpAddr)
|
|
waitTCPListening(httpProxyAddr)
|
|
waitTCPListening(smtpServerAddr)
|
|
|
|
exitCode := m.Run()
|
|
os.Remove(logFilePath)
|
|
os.RemoveAll(backupsPath)
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func TestBaseConnection(t *testing.T) {
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
_, err = client.ReadDir(testDir)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = client.RemoveDirectory(testDir)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(testDir)
|
|
assert.Error(t, err)
|
|
info, err := client.Stat(testDir)
|
|
if assert.NoError(t, err) {
|
|
assert.True(t, info.IsDir())
|
|
}
|
|
err = client.Rename(testDir, testDir)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "the rename source and target cannot be the same")
|
|
}
|
|
err = client.Rename(testDir, path.Join(testDir, "sub"))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.RemoveDirectory(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(testFileName)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
linkName := testFileName + ".link" //nolint:goconst
|
|
err = client.Rename(testFileName, testFileName)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "the rename source and target cannot be the same")
|
|
}
|
|
err = client.Symlink(testFileName, linkName)
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(testFileName, testFileName)
|
|
assert.Error(t, err)
|
|
info, err = client.Stat(testFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, int64(len(testFileContent)), info.Size())
|
|
assert.False(t, info.IsDir())
|
|
}
|
|
info, err = client.Lstat(linkName)
|
|
if assert.NoError(t, err) {
|
|
assert.NotEqual(t, int64(7), info.Size())
|
|
assert.True(t, info.Mode()&os.ModeSymlink != 0)
|
|
assert.False(t, info.IsDir())
|
|
}
|
|
err = client.RemoveDirectory(linkName)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
|
|
}
|
|
err = client.Remove(testFileName)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(linkName)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName, "test")
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
f, err = client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName, testFileName+"1")
|
|
assert.NoError(t, err)
|
|
err = client.Remove(testFileName + "1")
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory("missing")
|
|
assert.Error(t, err)
|
|
} else {
|
|
printLatestLogs(10)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRemoveAll(t *testing.T) {
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
webDavClient := getWebDavClient(user)
|
|
err = webDavClient.RemoveAll("/")
|
|
if assert.Error(t, err) {
|
|
assert.True(t, gowebdav.IsErrCode(err, http.StatusForbidden))
|
|
}
|
|
|
|
testDir := "baseDir"
|
|
err = webDavClient.RemoveAll(testDir)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, testFileName), 1234, client)
|
|
assert.NoError(t, err)
|
|
|
|
err = webDavClient.RemoveAll(path.Join(testDir, testFileName))
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join(testDir, testFileName))
|
|
assert.Error(t, err)
|
|
|
|
err = writeSFTPFile(path.Join(testDir, testFileName), 1234, client)
|
|
assert.NoError(t, err)
|
|
err = webDavClient.RemoveAll(testDir)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(testDir)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRelativeSymlinks(t *testing.T) {
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
linkName := testFileName + "_link" //nolint:goconst
|
|
err = client.Symlink("non-existent-file", linkName)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(linkName)
|
|
assert.NoError(t, err)
|
|
testDir := "sub"
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
f, err := client.Create(path.Join(testDir, testFileName))
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join(testDir, testFileName), linkName)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(linkName)
|
|
assert.NoError(t, err)
|
|
p, err := client.ReadLink(linkName)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, path.Join("/", testDir, testFileName), p)
|
|
err = client.Remove(linkName)
|
|
assert.NoError(t, err)
|
|
|
|
err = client.Symlink(testFileName, path.Join(testDir, linkName))
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join(testDir, linkName))
|
|
assert.NoError(t, err)
|
|
p, err = client.ReadLink(path.Join(testDir, linkName))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, path.Join("/", testDir, testFileName), p)
|
|
|
|
f, err = client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
|
|
err = client.Symlink(testFileName, linkName)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(linkName)
|
|
assert.NoError(t, err)
|
|
p, err = client.ReadLink(linkName)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, path.Join("/", testFileName), p)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCheckFsAfterUpdate(t *testing.T) {
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
// remove the home dir, it will not be re-created
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.Error(t, err)
|
|
} else {
|
|
printLatestLogs(10)
|
|
}
|
|
// update the user and login again, this time the home dir will be created
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestLoginAccessTime(t *testing.T) {
|
|
u := getTestUser()
|
|
u.Filters.AccessTime = []sdk.TimePeriod{
|
|
{
|
|
DayOfWeek: int(time.Now().Add(-25 * time.Hour).UTC().Weekday()),
|
|
From: "00:00",
|
|
To: "23:59",
|
|
},
|
|
}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
_, _, err = getSftpClient(user)
|
|
assert.Error(t, err)
|
|
|
|
user.Filters.AccessTime = []sdk.TimePeriod{
|
|
{
|
|
DayOfWeek: int(time.Now().UTC().Weekday()),
|
|
From: "00:00",
|
|
To: "23:59",
|
|
},
|
|
}
|
|
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err := checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestSetStat(t *testing.T) {
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
acmodTime := time.Now().Add(36 * time.Hour)
|
|
err = client.Chtimes(testFileName, acmodTime, acmodTime)
|
|
assert.NoError(t, err)
|
|
newFi, err := client.Lstat(testFileName)
|
|
assert.NoError(t, err)
|
|
diff := math.Abs(newFi.ModTime().Sub(acmodTime).Seconds())
|
|
assert.LessOrEqual(t, diff, float64(1))
|
|
if runtime.GOOS != osWindows {
|
|
err = client.Chown(testFileName, os.Getuid(), os.Getgid())
|
|
assert.NoError(t, err)
|
|
}
|
|
newPerm := os.FileMode(0666)
|
|
err = client.Chmod(testFileName, newPerm)
|
|
assert.NoError(t, err)
|
|
newFi, err = client.Lstat(testFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, newPerm, newFi.Mode().Perm())
|
|
}
|
|
err = client.Truncate(testFileName, 2)
|
|
assert.NoError(t, err)
|
|
info, err := client.Stat(testFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, int64(2), info.Size())
|
|
}
|
|
err = client.Remove(testFileName)
|
|
assert.NoError(t, err)
|
|
|
|
err = client.Truncate(testFileName, 0)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = client.Chtimes(testFileName, acmodTime, acmodTime)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
if runtime.GOOS != osWindows {
|
|
err = client.Chown(testFileName, os.Getuid(), os.Getgid())
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
err = client.Chmod(testFileName, newPerm)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCryptFsUserUploadErrorOverwrite(t *testing.T) {
|
|
u := getCryptFsUser()
|
|
u.QuotaSize = 6000
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
var buf []byte
|
|
for i := 0; i < 4000; i++ {
|
|
buf = append(buf, []byte("a")...)
|
|
}
|
|
bufSize := int64(len(buf))
|
|
reader := bytes.NewReader(buf)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
f, err := client.Create(testFileName + "_big")
|
|
assert.NoError(t, err)
|
|
n, err := io.Copy(f, reader)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, bufSize, n)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
encryptedSize, err := getEncryptedFileSize(bufSize)
|
|
assert.NoError(t, err)
|
|
expectedSize := encryptedSize
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedSize, user.UsedQuotaSize)
|
|
// now write a small file
|
|
f, err = client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
encryptedSize, err = getEncryptedFileSize(int64(len(testFileContent)))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedSize+encryptedSize, user.UsedQuotaSize)
|
|
// try to overwrite this file with a big one, this cause an overquota error
|
|
// the partial file is deleted and the quota updated
|
|
_, err = reader.Seek(0, io.SeekStart)
|
|
assert.NoError(t, err)
|
|
f, err = client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = io.Copy(f, reader)
|
|
assert.Error(t, err)
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedSize, user.UsedQuotaSize)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestChtimesOpenHandle(t *testing.T) {
|
|
localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getCryptFsUser()
|
|
cryptFsUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
f1, err := client.Create(testFileName + "1")
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
acmodTime := time.Now().Add(36 * time.Hour)
|
|
err = client.Chtimes(testFileName, acmodTime, acmodTime)
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
err = f.Close()
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
err = f1.Close()
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
info, err := client.Lstat(testFileName)
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
diff := math.Abs(info.ModTime().Sub(acmodTime).Seconds())
|
|
assert.LessOrEqual(t, diff, float64(1), "user %v", user.Username)
|
|
info1, err := client.Lstat(testFileName + "1")
|
|
assert.NoError(t, err, "user %v", user.Username)
|
|
diff = math.Abs(info1.ModTime().Sub(acmodTime).Seconds())
|
|
assert.Greater(t, diff, float64(86400), "user %v", user.Username)
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(localUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(cryptFsUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestWaitForConnections(t *testing.T) {
|
|
u := getTestUser()
|
|
u.UploadBandwidth = 128
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
testFileSize := int64(524288)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = common.CheckClosing()
|
|
assert.NoError(t, err)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
time.Sleep(1 * time.Second)
|
|
common.WaitForTransfers(10)
|
|
common.WaitForTransfers(0)
|
|
common.WaitForTransfers(10)
|
|
}()
|
|
|
|
err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
wg.Wait()
|
|
|
|
err = common.CheckClosing()
|
|
assert.EqualError(t, err, common.ErrShuttingDown.Error())
|
|
|
|
_, err = client.Stat(testFileName)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), common.ErrShuttingDown.Error())
|
|
}
|
|
}
|
|
|
|
_, _, err = getSftpClient(user)
|
|
assert.Error(t, err)
|
|
|
|
err = common.Initialize(common.Config, 0)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
info, err := client.Stat(testFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, testFileSize, info.Size())
|
|
}
|
|
err = client.Remove(testFileName)
|
|
assert.NoError(t, err)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
time.Sleep(1 * time.Second)
|
|
common.WaitForTransfers(1)
|
|
}()
|
|
|
|
err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
|
|
// we don't have an error here because the service won't really stop
|
|
assert.NoError(t, err)
|
|
wg.Wait()
|
|
}
|
|
|
|
err = common.Initialize(common.Config, 0)
|
|
assert.NoError(t, err)
|
|
|
|
common.WaitForTransfers(1)
|
|
|
|
err = common.Initialize(common.Config, 0)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCheckParentDirs(t *testing.T) {
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
testDir := "/path/to/sub/dir"
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
_, err = client.Stat(testDir)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
c := common.NewBaseConnection(xid.New().String(), common.ProtocolSFTP, "", "", user)
|
|
err = c.CheckParentDirs(testDir)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(testDir)
|
|
assert.NoError(t, err)
|
|
err = c.CheckParentDirs(testDir)
|
|
assert.NoError(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
u := getTestUser()
|
|
u.Permissions["/"] = []string{dataprovider.PermUpload, dataprovider.PermListItems, dataprovider.PermDownload}
|
|
user, _, err = httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
c := common.NewBaseConnection(xid.New().String(), common.ProtocolSFTP, "", "", user)
|
|
err = c.CheckParentDirs(testDir)
|
|
assert.ErrorIs(t, err, sftp.ErrSSHFxPermissionDenied)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPermissionErrors(t *testing.T) {
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getTestSFTPUser()
|
|
subDir := "/sub"
|
|
u.Permissions[subDir] = nil
|
|
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = client.MkdirAll(path.Join(subDir, subDir))
|
|
assert.NoError(t, err)
|
|
f, err := client.Create(path.Join(subDir, subDir, testFileName))
|
|
if assert.NoError(t, err) {
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
}
|
|
conn, client, err = getSftpClient(sftpUser)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
_, err = client.ReadDir(subDir)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Mkdir(path.Join(subDir, subDir))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.RemoveDirectory(path.Join(subDir, subDir))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Symlink("test", path.Join(subDir, subDir))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Chmod(path.Join(subDir, subDir), os.ModePerm)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Chown(path.Join(subDir, subDir), os.Getuid(), os.Getgid())
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Chtimes(path.Join(subDir, subDir), time.Now(), time.Now())
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Truncate(path.Join(subDir, subDir), 0)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Remove(path.Join(subDir, subDir, testFileName))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
}
|
|
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestHiddenPatternFilter(t *testing.T) {
|
|
deniedDir := "/denied_hidden"
|
|
u := getTestUser()
|
|
u.Filters.FilePatterns = []sdk.PatternsFilter{
|
|
{
|
|
Path: deniedDir,
|
|
DeniedPatterns: []string{"*.txt", "beta*"},
|
|
DenyPolicy: sdk.DenyPolicyHide,
|
|
},
|
|
}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
dirName := "beta"
|
|
subDirName := "testDir"
|
|
testFile := filepath.Join(u.GetHomeDir(), deniedDir, "file.txt")
|
|
testFile1 := filepath.Join(u.GetHomeDir(), deniedDir, "beta.txt")
|
|
testHiddenFile := filepath.Join(u.GetHomeDir(), deniedDir, dirName, subDirName, "hidden.jpg")
|
|
err = os.MkdirAll(filepath.Join(u.GetHomeDir(), deniedDir), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(testFile, testFileContent, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(testFile1, testFileContent, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.MkdirAll(filepath.Join(u.GetHomeDir(), deniedDir, dirName, subDirName), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(testHiddenFile, testFileContent, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
files, err := client.ReadDir(deniedDir)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, files, 0)
|
|
err = client.Remove(path.Join(deniedDir, filepath.Base(testFile)))
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = client.Chtimes(path.Join(deniedDir, filepath.Base(testFile)), time.Now(), time.Now())
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
_, err = client.Stat(path.Join(deniedDir, filepath.Base(testFile1)))
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = client.RemoveDirectory(path.Join(deniedDir, dirName))
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = client.Rename(path.Join(deniedDir, dirName), path.Join(deniedDir, "newname"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Mkdir(path.Join(deniedDir, "beta1"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join(deniedDir, "afile.txt"), 1024, client)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join(deniedDir, dirName, subDirName, "afile.jpg"), 1024, client)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
_, err = client.Open(path.Join(deniedDir, dirName, subDirName, filepath.Base(testHiddenFile)))
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = client.Symlink(path.Join(deniedDir, dirName), dirName)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
err = writeSFTPFile(path.Join(deniedDir, testFileName), 1024, client)
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join(deniedDir, testFileName), path.Join(deniedDir, "symlink.txt"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
files, err = client.ReadDir(deniedDir)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, files, 1)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
u.Filters.FilePatterns = []sdk.PatternsFilter{
|
|
{
|
|
Path: deniedDir,
|
|
DeniedPatterns: []string{"*.txt", "beta*"},
|
|
DenyPolicy: sdk.DenyPolicyDefault,
|
|
},
|
|
}
|
|
user, _, err = httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
files, err := client.ReadDir(deniedDir)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, files, 4)
|
|
_, err = client.Stat(path.Join(deniedDir, filepath.Base(testFile)))
|
|
assert.NoError(t, err)
|
|
err = client.Chtimes(path.Join(deniedDir, filepath.Base(testFile)), time.Now(), time.Now())
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Mkdir(path.Join(deniedDir, "beta2"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join(deniedDir, "afile2.txt"), 1024, client)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Symlink(path.Join(deniedDir, testFileName), path.Join(deniedDir, "link.txt"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join(deniedDir, dirName, subDirName, "afile.jpg"), 1024, client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Open(path.Join(deniedDir, dirName, subDirName, filepath.Base(testHiddenFile)))
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestHiddenRoot(t *testing.T) {
|
|
// only the "/ftp" directory is allowed and visibile in the "/" path
|
|
// within /ftp any file/directory is allowed and visibile
|
|
u := getTestUser()
|
|
u.Filters.FilePatterns = []sdk.PatternsFilter{
|
|
{
|
|
Path: "/",
|
|
AllowedPatterns: []string{"ftp"},
|
|
DenyPolicy: sdk.DenyPolicyHide,
|
|
},
|
|
{
|
|
Path: "/ftp",
|
|
AllowedPatterns: []string{"*"},
|
|
},
|
|
}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
err = os.MkdirAll(filepath.Join(user.HomeDir, fmt.Sprintf("ftp%d", i)), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
}
|
|
err = os.WriteFile(filepath.Join(user.HomeDir, testFileName), []byte(""), 0666)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(user.HomeDir, "ftp.txt"), []byte(""), 0666)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = client.Mkdir("ftp")
|
|
assert.NoError(t, err)
|
|
entries, err := client.ReadDir("/")
|
|
assert.NoError(t, err)
|
|
if assert.Len(t, entries, 1) {
|
|
assert.Equal(t, "ftp", entries[0].Name())
|
|
}
|
|
_, err = client.Stat(".")
|
|
assert.NoError(t, err)
|
|
for _, name := range []string{testFileName, "ftp.txt"} {
|
|
_, err = client.Stat(name)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
for i := 0; i < 10; i++ {
|
|
_, err = client.Stat(fmt.Sprintf("ftp%d", i))
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile("ftp123", 4096, client)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(testFileName, testFileName+"_rename") //nolint:goconst
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join("/ftp", testFileName), 4096, client)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir("/ftp/dir")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join("/ftp", testFileName), path.Join("/ftp/dir", testFileName))
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestFileNotAllowedErrors(t *testing.T) {
|
|
deniedDir := "/denied"
|
|
u := getTestUser()
|
|
u.Filters.FilePatterns = []sdk.PatternsFilter{
|
|
{
|
|
Path: deniedDir,
|
|
DeniedPatterns: []string{"*.txt"},
|
|
},
|
|
}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFile := filepath.Join(u.GetHomeDir(), deniedDir, "file.txt")
|
|
err = os.MkdirAll(filepath.Join(u.GetHomeDir(), deniedDir), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(testFile, testFileContent, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(path.Join(deniedDir, "file.txt"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join(deniedDir, "file.txt"), path.Join(deniedDir, "file1.txt"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRootDirVirtualFolder(t *testing.T) {
|
|
mappedPath1 := filepath.Join(os.TempDir(), "mapped1")
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: filepath.Base(mappedPath1),
|
|
MappedPath: mappedPath1,
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.CryptedFilesystemProvider,
|
|
CryptConfig: vfs.CryptFsConfig{
|
|
Passphrase: kms.NewPlainSecret("cryptsecret"),
|
|
},
|
|
},
|
|
}
|
|
mappedPath2 := filepath.Join(os.TempDir(), "mapped2")
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: filepath.Base(mappedPath2),
|
|
MappedPath: mappedPath2,
|
|
}
|
|
folder1, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
folder2, _, err := httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
u := getTestUser()
|
|
u.QuotaFiles = 1000
|
|
u.UploadDataTransfer = 1000
|
|
u.DownloadDataTransfer = 5000
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder1.Name,
|
|
},
|
|
VirtualPath: "/",
|
|
QuotaFiles: 1000,
|
|
})
|
|
vdirPath2 := "/vmapped"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder2.Name,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f, err := user.GetVirtualFolderForPath("/")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "/", f.VirtualPath)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Create(testFileName)
|
|
if assert.NoError(t, err) {
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.NoFileExists(t, filepath.Join(user.HomeDir, testFileName))
|
|
assert.FileExists(t, filepath.Join(mappedPath1, testFileName))
|
|
entries, err := client.ReadDir(".")
|
|
if assert.NoError(t, err) {
|
|
assert.Len(t, entries, 2)
|
|
}
|
|
|
|
user, _, err := httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, user.UsedQuotaFiles)
|
|
folder, _, err := httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, folder.UsedQuotaFiles)
|
|
|
|
f, err = client.Create(path.Join(vdirPath2, testFileName))
|
|
if assert.NoError(t, err) {
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
folder, _, err = httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, folder.UsedQuotaFiles)
|
|
|
|
err = client.Rename(testFileName, path.Join(vdirPath2, testFileName+"_rename"))
|
|
assert.Error(t, err)
|
|
err = client.Rename(path.Join(vdirPath2, testFileName), testFileName+"_rename")
|
|
assert.Error(t, err)
|
|
err = client.Rename(testFileName, testFileName+"_rename")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testFileName+"_rename"))
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder1.Name}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder2.Name}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestTruncateQuotaLimits(t *testing.T) {
|
|
mappedPath1 := filepath.Join(os.TempDir(), "mapped1")
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: filepath.Base(mappedPath1),
|
|
MappedPath: mappedPath1,
|
|
}
|
|
folder1, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
mappedPath2 := filepath.Join(os.TempDir(), "mapped2")
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: filepath.Base(mappedPath2),
|
|
MappedPath: mappedPath2,
|
|
}
|
|
folder2, _, err := httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getTestUser()
|
|
u.QuotaSize = 20
|
|
u.UploadDataTransfer = 1000
|
|
u.DownloadDataTransfer = 5000
|
|
vdirPath1 := "/vmapped1"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder1.Name,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
QuotaFiles: 10,
|
|
})
|
|
vdirPath2 := "/vmapped2"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder2.Name,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u = getTestSFTPUser()
|
|
u.QuotaSize = 20
|
|
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
for _, user := range []dataprovider.User{localUser, sftpUser} {
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
f, err := client.OpenFile(testFileName, os.O_WRONLY|os.O_CREATE)
|
|
if assert.NoError(t, err) {
|
|
n, err := f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Truncate(2)
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles := 0
|
|
expectedQuotaSize := int64(2)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
|
_, err = f.Seek(expectedQuotaSize, io.SeekStart)
|
|
assert.NoError(t, err)
|
|
n, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Truncate(5)
|
|
assert.NoError(t, err)
|
|
expectedQuotaSize = int64(5)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
|
_, err = f.Seek(expectedQuotaSize, io.SeekStart)
|
|
assert.NoError(t, err)
|
|
n, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles = 1
|
|
expectedQuotaSize = int64(5) + int64(len(testFileContent))
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
|
}
|
|
// now truncate by path
|
|
err = client.Truncate(testFileName, 5)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(5), user.UsedQuotaSize)
|
|
// now open an existing file without truncate it, quota should not change
|
|
f, err = client.OpenFile(testFileName, os.O_WRONLY)
|
|
if assert.NoError(t, err) {
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(5), user.UsedQuotaSize)
|
|
}
|
|
// open the file truncating it
|
|
f, err = client.OpenFile(testFileName, os.O_WRONLY|os.O_TRUNC)
|
|
if assert.NoError(t, err) {
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), user.UsedQuotaSize)
|
|
}
|
|
// now test max write size
|
|
f, err = client.OpenFile(testFileName, os.O_WRONLY)
|
|
if assert.NoError(t, err) {
|
|
n, err := f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Truncate(11)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(11), user.UsedQuotaSize)
|
|
_, err = f.Seek(int64(11), io.SeekStart)
|
|
assert.NoError(t, err)
|
|
n, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Truncate(5)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(5), user.UsedQuotaSize)
|
|
_, err = f.Seek(int64(5), io.SeekStart)
|
|
assert.NoError(t, err)
|
|
n, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Truncate(12)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(12), user.UsedQuotaSize)
|
|
_, err = f.Seek(int64(12), io.SeekStart)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
|
|
}
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
// the file is deleted
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), user.UsedQuotaSize)
|
|
}
|
|
|
|
if user.Username == defaultUsername {
|
|
// basic test inside a virtual folder
|
|
vfileName1 := path.Join(vdirPath1, testFileName)
|
|
f, err = client.OpenFile(vfileName1, os.O_WRONLY|os.O_CREATE)
|
|
if assert.NoError(t, err) {
|
|
n, err := f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Truncate(2)
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles := 0
|
|
expectedQuotaSize := int64(2)
|
|
fold, _, err := httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
|
|
assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles = 1
|
|
fold, _, err = httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
|
|
assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
|
|
}
|
|
err = client.Truncate(vfileName1, 1)
|
|
assert.NoError(t, err)
|
|
fold, _, err := httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(1), fold.UsedQuotaSize)
|
|
assert.Equal(t, 1, fold.UsedQuotaFiles)
|
|
// now test on vdirPath2, the folder quota is included in the user's quota
|
|
vfileName2 := path.Join(vdirPath2, testFileName)
|
|
f, err = client.OpenFile(vfileName2, os.O_WRONLY|os.O_CREATE)
|
|
if assert.NoError(t, err) {
|
|
n, err := f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(testFileContent), n)
|
|
err = f.Truncate(3)
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles := 0
|
|
expectedQuotaSize := int64(3)
|
|
fold, _, err := httpdtest.GetFolderByName(folder2.Name, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), fold.UsedQuotaSize)
|
|
assert.Equal(t, 0, fold.UsedQuotaFiles)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles = 1
|
|
fold, _, err = httpdtest.GetFolderByName(folder2.Name, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), fold.UsedQuotaSize)
|
|
assert.Equal(t, 0, fold.UsedQuotaFiles)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
|
}
|
|
|
|
// cleanup
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
if user.Username == defaultUsername {
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
user.Password = defaultPassword
|
|
user.QuotaSize = 0
|
|
user.ID = 0
|
|
user.CreatedAt = 0
|
|
_, resp, err := httpdtest.AddUser(user, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(localUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(folder1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(folder2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestVirtualFoldersQuotaRenameOverwrite(t *testing.T) {
|
|
testFileSize := int64(131072)
|
|
testFileSize1 := int64(65537)
|
|
testFileName1 := "test_file1.dat" //nolint:goconst
|
|
u := getTestUser()
|
|
u.QuotaFiles = 0
|
|
u.QuotaSize = 0
|
|
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
vdirPath1 := "/vdir1" //nolint:goconst
|
|
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
vdirPath2 := "/vdir2" //nolint:goconst
|
|
mappedPath3 := filepath.Join(os.TempDir(), "vdir3")
|
|
folderName3 := filepath.Base(mappedPath3)
|
|
vdirPath3 := "/vdir3"
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f3 := vfs.BaseVirtualFolder{
|
|
Name: folderName3,
|
|
MappedPath: mappedPath3,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
QuotaFiles: 2,
|
|
QuotaSize: 0,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
QuotaFiles: 0,
|
|
QuotaSize: testFileSize + testFileSize1 + 1,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName3,
|
|
},
|
|
VirtualPath: vdirPath3,
|
|
QuotaFiles: 2,
|
|
QuotaSize: testFileSize * 2,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Open(path.Join(vdirPath1, testFileName))
|
|
assert.NoError(t, err)
|
|
contents, err := io.ReadAll(f)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
assert.Len(t, contents, int(testFileSize))
|
|
err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName1, testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath3, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath3, testFileName+"1"), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName, path.Join(vdirPath1, testFileName+".rename")) //nolint:goconst
|
|
assert.Error(t, err)
|
|
// we overwrite an existing file and we have unlimited size
|
|
err = client.Rename(testFileName, path.Join(vdirPath1, testFileName))
|
|
assert.NoError(t, err)
|
|
// we have no space and we try to overwrite a bigger file with a smaller one, this should succeed
|
|
err = client.Rename(testFileName1, path.Join(vdirPath2, testFileName))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
// we have no space and we try to overwrite a smaller file with a bigger one, this should fail
|
|
err = client.Rename(testFileName, path.Join(vdirPath2, testFileName1))
|
|
assert.Error(t, err)
|
|
fi, err := client.Stat(path.Join(vdirPath1, testFileName1))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, testFileSize1, fi.Size())
|
|
}
|
|
// we are overquota inside vdir3 size 2/2 and size 262144/262144
|
|
err = client.Rename(path.Join(vdirPath1, testFileName1), path.Join(vdirPath3, testFileName1+".rename"))
|
|
assert.Error(t, err)
|
|
// we overwrite an existing file and we have enough size
|
|
err = client.Rename(path.Join(vdirPath1, testFileName1), path.Join(vdirPath3, testFileName))
|
|
assert.NoError(t, err)
|
|
testFileName2 := "test_file2.dat"
|
|
err = writeSFTPFile(testFileName2, testFileSize+testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
// we overwrite an existing file and we haven't enough size
|
|
err = client.Rename(testFileName2, path.Join(vdirPath3, testFileName))
|
|
assert.Error(t, err)
|
|
// now remove a file from vdir3, create a dir with 2 files and try to rename it in vdir3
|
|
// this will fail since the rename will result in 3 files inside vdir3 and quota limits only
|
|
// allow 2 total files there
|
|
err = client.Remove(path.Join(vdirPath3, testFileName+"1"))
|
|
assert.NoError(t, err)
|
|
aDir := "a dir"
|
|
err = client.Mkdir(aDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(aDir, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(aDir, testFileName1+"1"), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(aDir, path.Join(vdirPath3, aDir))
|
|
assert.Error(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName3}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath3)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestQuotaRenameOverwrite(t *testing.T) {
|
|
u := getTestUser()
|
|
u.QuotaFiles = 100
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileSize := int64(131072)
|
|
testFileSize1 := int64(65537)
|
|
testFileName1 := "test_file1.dat"
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Open(testFileName)
|
|
assert.NoError(t, err)
|
|
contents := make([]byte, testFileSize)
|
|
n, err := io.ReadFull(f, contents)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int(testFileSize), n)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName1, testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), user.UsedDownloadDataTransfer)
|
|
assert.Equal(t, int64(0), user.UsedUploadDataTransfer)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
err = client.Rename(testFileName, testFileName1)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), user.UsedDownloadDataTransfer)
|
|
assert.Equal(t, int64(0), user.UsedUploadDataTransfer)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize, user.UsedQuotaSize)
|
|
err = client.Remove(testFileName1)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName1, testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName1, testFileName)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1, user.UsedQuotaSize)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestVirtualFoldersQuotaValues(t *testing.T) {
|
|
u := getTestUser()
|
|
u.QuotaFiles = 100
|
|
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
|
vdirPath1 := "/vdir1"
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
|
|
vdirPath2 := "/vdir2"
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
// quota is included in the user's one
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
// quota is unlimited and excluded from user's one
|
|
QuotaFiles: 0,
|
|
QuotaSize: 0,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileSize := int64(131072)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
// we copy the same file two times to test quota update on file overwrite
|
|
err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles := 2
|
|
expectedQuotaSize := testFileSize * 2
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
|
f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
|
|
err = client.Remove(path.Join(vdirPath1, testFileName))
|
|
assert.NoError(t, err)
|
|
err = client.Remove(path.Join(vdirPath2, testFileName))
|
|
assert.NoError(t, err)
|
|
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestQuotaRenameInsideSameVirtualFolder(t *testing.T) {
|
|
u := getTestUser()
|
|
u.QuotaFiles = 100
|
|
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
|
vdirPath1 := "/vdir1"
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
|
|
vdirPath2 := "/vdir2"
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
// quota is included in the user's one
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
// quota is unlimited and excluded from user's one
|
|
QuotaFiles: 0,
|
|
QuotaSize: 0,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileName1 := "test_file1.dat"
|
|
testFileSize := int64(131072)
|
|
testFileSize1 := int64(65535)
|
|
dir1 := "dir1" //nolint:goconst
|
|
dir2 := "dir2" //nolint:goconst
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath1, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath1, dir2))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir2))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 2, f.UsedQuotaFiles)
|
|
// initial files:
|
|
// - vdir1/dir1/testFileName
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName
|
|
// - vdir2/dir2/testFileName1
|
|
//
|
|
// rename a file inside vdir1 it is included inside user quota, so we have:
|
|
// - vdir1/dir1/testFileName.rename
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName
|
|
// - vdir2/dir2/testFileName1
|
|
err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(vdirPath1, dir1, testFileName+".rename"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
// rename a file inside vdir2, it isn't included inside user quota, so we have:
|
|
// - vdir1/dir1/testFileName.rename
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName.rename
|
|
// - vdir2/dir2/testFileName1
|
|
err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(vdirPath2, dir1, testFileName+".rename"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 2, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
// rename a file inside vdir2 overwriting an existing, we now have:
|
|
// - vdir1/dir1/testFileName.rename
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName.rename (initial testFileName1)
|
|
err = client.Rename(path.Join(vdirPath2, dir2, testFileName1), path.Join(vdirPath2, dir1, testFileName+".rename"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
// rename a file inside vdir1 overwriting an existing, we now have:
|
|
// - vdir1/dir1/testFileName.rename (initial testFileName1)
|
|
// - vdir2/dir1/testFileName.rename (initial testFileName1)
|
|
err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(vdirPath1, dir1, testFileName+".rename"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
// rename a directory inside the same virtual folder, quota should not change
|
|
err = client.RemoveDirectory(path.Join(vdirPath1, dir2))
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory(path.Join(vdirPath2, dir2))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirPath1, dir1), path.Join(vdirPath1, dir2))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirPath2, dir1), path.Join(vdirPath2, dir2))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestQuotaRenameBetweenVirtualFolder(t *testing.T) {
|
|
u := getTestUser()
|
|
u.QuotaFiles = 100
|
|
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
vdirPath1 := "/vdir1"
|
|
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
vdirPath2 := "/vdir2"
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
// quota is included in the user's one
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
// quota is unlimited and excluded from user's one
|
|
QuotaFiles: 0,
|
|
QuotaSize: 0,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileName1 := "test_file1.dat"
|
|
testFileSize := int64(131072)
|
|
testFileSize1 := int64(65535)
|
|
dir1 := "dir1"
|
|
dir2 := "dir2"
|
|
err = client.Mkdir(path.Join(vdirPath1, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath1, dir2))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir2))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
// initial files:
|
|
// - vdir1/dir1/testFileName
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName
|
|
// - vdir2/dir2/testFileName1
|
|
//
|
|
// rename a file from vdir1 to vdir2, vdir1 is included inside user quota, so we have:
|
|
// - vdir1/dir1/testFileName
|
|
// - vdir2/dir1/testFileName
|
|
// - vdir2/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName1.rename
|
|
err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(vdirPath2, dir1, testFileName1+".rename"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize, user.UsedQuotaSize)
|
|
f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize+testFileSize1+testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 3, f.UsedQuotaFiles)
|
|
// rename a file from vdir2 to vdir1, vdir2 is not included inside user quota, so we have:
|
|
// - vdir1/dir1/testFileName
|
|
// - vdir1/dir2/testFileName.rename
|
|
// - vdir2/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName1.rename
|
|
err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(vdirPath1, dir2, testFileName+".rename"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize*2, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1*2, f.UsedQuotaSize)
|
|
assert.Equal(t, 2, f.UsedQuotaFiles)
|
|
// rename a file from vdir1 to vdir2 overwriting an existing file, vdir1 is included inside user quota, so we have:
|
|
// - vdir1/dir2/testFileName.rename
|
|
// - vdir2/dir2/testFileName1 (is the initial testFileName)
|
|
// - vdir2/dir1/testFileName1.rename
|
|
err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(vdirPath2, dir2, testFileName1))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1+testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 2, f.UsedQuotaFiles)
|
|
// rename a file from vdir2 to vdir1 overwriting an existing file, vdir2 is not included inside user quota, so we have:
|
|
// - vdir1/dir2/testFileName.rename (is the initial testFileName1)
|
|
// - vdir2/dir2/testFileName1 (is the initial testFileName)
|
|
err = client.Rename(path.Join(vdirPath2, dir1, testFileName1+".rename"), path.Join(vdirPath1, dir2, testFileName+".rename"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName+"1.dupl"), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory(path.Join(vdirPath1, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory(path.Join(vdirPath2, dir1))
|
|
assert.NoError(t, err)
|
|
// - vdir1/dir2/testFileName.rename (initial testFileName1)
|
|
// - vdir1/dir2/testFileName
|
|
// - vdir2/dir2/testFileName1 (initial testFileName)
|
|
// - vdir2/dir2/testFileName (initial testFileName1)
|
|
// - vdir2/dir2/testFileName1.dupl
|
|
// rename directories between the two virtual folders
|
|
err = client.Rename(path.Join(vdirPath2, dir2), path.Join(vdirPath1, dir1))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1*3+testFileSize*2, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
// now move on vpath2
|
|
err = client.Rename(path.Join(vdirPath1, dir2), path.Join(vdirPath2, dir1))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1*2+testFileSize, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 2, f.UsedQuotaFiles)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestQuotaRenameFromVirtualFolder(t *testing.T) {
|
|
u := getTestUser()
|
|
u.QuotaFiles = 100
|
|
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
vdirPath1 := "/vdir1"
|
|
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
vdirPath2 := "/vdir2"
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
// quota is included in the user's one
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
// quota is unlimited and excluded from user's one
|
|
QuotaFiles: 0,
|
|
QuotaSize: 0,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileName1 := "test_file1.dat"
|
|
testFileSize := int64(131072)
|
|
testFileSize1 := int64(65535)
|
|
dir1 := "dir1"
|
|
dir2 := "dir2"
|
|
err = client.Mkdir(path.Join(vdirPath1, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath1, dir2))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir2))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
// initial files:
|
|
// - vdir1/dir1/testFileName
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName
|
|
// - vdir2/dir2/testFileName1
|
|
//
|
|
// rename a file from vdir1 to the user home dir, vdir1 is included in user quota so we have:
|
|
// - testFileName
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName
|
|
// - vdir2/dir2/testFileName1
|
|
err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(testFileName))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 2, f.UsedQuotaFiles)
|
|
// rename a file from vdir2 to the user home dir, vdir2 is not included in user quota so we have:
|
|
// - testFileName
|
|
// - testFileName1
|
|
// - vdir1/dir2/testFileName1
|
|
// - vdir2/dir1/testFileName
|
|
err = client.Rename(path.Join(vdirPath2, dir2, testFileName1), path.Join(testFileName1))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
// rename a file from vdir1 to the user home dir overwriting an existing file, vdir1 is included in user quota so we have:
|
|
// - testFileName (initial testFileName1)
|
|
// - testFileName1
|
|
// - vdir2/dir1/testFileName
|
|
err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(testFileName))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
// rename a file from vdir2 to the user home dir overwriting an existing file, vdir2 is not included in user quota so we have:
|
|
// - testFileName (initial testFileName1)
|
|
// - testFileName1 (initial testFileName)
|
|
err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(testFileName1))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
// dir rename
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
// - testFileName (initial testFileName1)
|
|
// - testFileName1 (initial testFileName)
|
|
// - vdir1/dir1/testFileName
|
|
// - vdir1/dir1/testFileName1
|
|
// - dir1/testFileName
|
|
// - dir1/testFileName1
|
|
err = client.Rename(path.Join(vdirPath2, dir1), dir1)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 6, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
// - testFileName (initial testFileName1)
|
|
// - testFileName1 (initial testFileName)
|
|
// - dir2/testFileName
|
|
// - dir2/testFileName1
|
|
// - dir1/testFileName
|
|
// - dir1/testFileName1
|
|
err = client.Rename(path.Join(vdirPath1, dir1), dir2)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 6, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestQuotaRenameToVirtualFolder(t *testing.T) {
|
|
u := getTestUser()
|
|
u.QuotaFiles = 100
|
|
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
vdirPath1 := "/vdir1"
|
|
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
vdirPath2 := "/vdir2"
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
// quota is included in the user's one
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
// quota is unlimited and excluded from user's one
|
|
QuotaFiles: 0,
|
|
QuotaSize: 0,
|
|
})
|
|
u.Permissions[vdirPath1] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload,
|
|
dataprovider.PermOverwrite, dataprovider.PermDelete, dataprovider.PermCreateSymlinks, dataprovider.PermCreateDirs,
|
|
dataprovider.PermRename}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileName1 := "test_file1.dat"
|
|
testFileSize := int64(131072)
|
|
testFileSize1 := int64(65535)
|
|
dir1 := "dir1"
|
|
dir2 := "dir2"
|
|
err = client.Mkdir(path.Join(vdirPath1, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath1, dir2))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir1))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, dir2))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName1, testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
// initial files:
|
|
// - testFileName
|
|
// - testFileName1
|
|
//
|
|
// rename a file from user home dir to vdir1, vdir1 is included in user quota so we have:
|
|
// - testFileName
|
|
// - /vdir1/dir1/testFileName1
|
|
err = client.Rename(testFileName1, path.Join(vdirPath1, dir1, testFileName1))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
// rename a file from user home dir to vdir2, vdir2 is not included in user quota so we have:
|
|
// - /vdir2/dir1/testFileName
|
|
// - /vdir1/dir1/testFileName1
|
|
err = client.Rename(testFileName, path.Join(vdirPath2, dir1, testFileName))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
// upload two new files to the user home dir so we have:
|
|
// - testFileName
|
|
// - testFileName1
|
|
// - /vdir1/dir1/testFileName1
|
|
// - /vdir2/dir1/testFileName
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName1, testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1+testFileSize1, user.UsedQuotaSize)
|
|
// rename a file from user home dir to vdir1 overwriting an existing file, vdir1 is included in user quota so we have:
|
|
// - testFileName1
|
|
// - /vdir1/dir1/testFileName1 (initial testFileName)
|
|
// - /vdir2/dir1/testFileName
|
|
err = client.Rename(testFileName, path.Join(vdirPath1, dir1, testFileName1))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
// rename a file from user home dir to vdir2 overwriting an existing file, vdir2 is not included in user quota so we have:
|
|
// - /vdir1/dir1/testFileName1 (initial testFileName)
|
|
// - /vdir2/dir1/testFileName (initial testFileName1)
|
|
err = client.Rename(testFileName1, path.Join(vdirPath2, dir1, testFileName))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
|
|
err = client.Mkdir(dir1)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(dir1, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
// - /dir1/testFileName
|
|
// - /dir1/testFileName1
|
|
// - /vdir1/dir1/testFileName1 (initial testFileName)
|
|
// - /vdir2/dir1/testFileName (initial testFileName1)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
// - /vdir1/adir/testFileName
|
|
// - /vdir1/adir/testFileName1
|
|
// - /vdir1/dir1/testFileName1 (initial testFileName)
|
|
// - /vdir2/dir1/testFileName (initial testFileName1)
|
|
err = client.Rename(dir1, path.Join(vdirPath1, "adir"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1, f.UsedQuotaSize)
|
|
assert.Equal(t, 1, f.UsedQuotaFiles)
|
|
err = client.Mkdir(dir1)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(dir1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(dir1, testFileName1), testFileSize1, client)
|
|
assert.NoError(t, err)
|
|
// - /vdir1/adir/testFileName
|
|
// - /vdir1/adir/testFileName1
|
|
// - /vdir1/dir1/testFileName1 (initial testFileName)
|
|
// - /vdir2/dir1/testFileName (initial testFileName1)
|
|
// - /vdir2/adir/testFileName
|
|
// - /vdir2/adir/testFileName1
|
|
err = client.Rename(dir1, path.Join(vdirPath2, "adir"))
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, user.UsedQuotaFiles)
|
|
assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
|
|
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(0), f.UsedQuotaSize)
|
|
assert.Equal(t, 0, f.UsedQuotaFiles)
|
|
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, testFileSize1*2+testFileSize, f.UsedQuotaSize)
|
|
assert.Equal(t, 3, f.UsedQuotaFiles)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestTransferQuotaLimits(t *testing.T) {
|
|
u := getTestUser()
|
|
u.TotalDataTransfer = 1
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
testFileSize := int64(524288)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Open(testFileName)
|
|
assert.NoError(t, err)
|
|
contents := make([]byte, testFileSize)
|
|
n, err := io.ReadFull(f, contents)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int(testFileSize), n)
|
|
assert.Len(t, contents, int(testFileSize))
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
_, err = client.Open(testFileName)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
|
|
assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
|
|
}
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
|
|
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
|
|
}
|
|
}
|
|
// test the limit while uploading/downloading
|
|
user.TotalDataTransfer = 0
|
|
user.UploadDataTransfer = 1
|
|
user.DownloadDataTransfer = 1
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
testFileSize := int64(450000)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Open(testFileName)
|
|
if assert.NoError(t, err) {
|
|
_, err = io.Copy(io.Discard, f)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
}
|
|
f, err = client.Open(testFileName)
|
|
if assert.NoError(t, err) {
|
|
_, err = io.Copy(io.Discard, f)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
|
|
assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
|
|
}
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
|
|
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestVirtualFoldersLink(t *testing.T) {
|
|
u := getTestUser()
|
|
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
vdirPath1 := "/vdir1"
|
|
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
vdirPath2 := "/vdir2"
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vdirPath1,
|
|
// quota is included in the user's one
|
|
QuotaFiles: -1,
|
|
QuotaSize: -1,
|
|
})
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vdirPath2,
|
|
// quota is unlimited and excluded from user's one
|
|
QuotaFiles: 0,
|
|
QuotaSize: 0,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileSize := int64(131072)
|
|
testDir := "adir"
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath1, testDir))
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(path.Join(vdirPath2, testDir))
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(testFileName, testFileName+".link")
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testFileName+".link"))
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testDir, testFileName+".link"))
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testFileName+".link"))
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testDir, testFileName+".link"))
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testFileName+".link1")) //nolint:goconst
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testDir, testFileName+".link1"))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testFileName+".link1"))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(path.Join(vdirPath1, testFileName), testFileName+".link1")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(path.Join(vdirPath2, testFileName), testFileName+".link1")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath1, testFileName+".link1"))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink("/", "/roolink")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Symlink(testFileName, "/")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Symlink(testFileName, vdirPath1)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Symlink(vdirPath1, testFileName+".link2")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath1)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath2)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCrossFolderRename(t *testing.T) {
|
|
folder1 := "folder1"
|
|
folder2 := "folder2"
|
|
folder3 := "folder3"
|
|
folder4 := "folder4"
|
|
folder5 := "folder5"
|
|
folder6 := "folder6"
|
|
folder7 := "folder7"
|
|
|
|
baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folder1,
|
|
MappedPath: filepath.Join(os.TempDir(), folder1),
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.CryptedFilesystemProvider,
|
|
CryptConfig: vfs.CryptFsConfig{
|
|
Passphrase: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folder2,
|
|
MappedPath: filepath.Join(os.TempDir(), folder2),
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.CryptedFilesystemProvider,
|
|
CryptConfig: vfs.CryptFsConfig{
|
|
Passphrase: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f3 := vfs.BaseVirtualFolder{
|
|
Name: folder3,
|
|
MappedPath: filepath.Join(os.TempDir(), folder3),
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.CryptedFilesystemProvider,
|
|
CryptConfig: vfs.CryptFsConfig{
|
|
Passphrase: kms.NewPlainSecret(defaultPassword + "mod"),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f4 := vfs.BaseVirtualFolder{
|
|
Name: folder4,
|
|
MappedPath: filepath.Join(os.TempDir(), folder4),
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: sftpServerAddr,
|
|
Username: baseUser.Username,
|
|
Prefix: path.Join("/", folder4),
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f4, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f5 := vfs.BaseVirtualFolder{
|
|
Name: folder5,
|
|
MappedPath: filepath.Join(os.TempDir(), folder5),
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: sftpServerAddr,
|
|
Username: baseUser.Username,
|
|
Prefix: path.Join("/", folder5),
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f5, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f6 := vfs.BaseVirtualFolder{
|
|
Name: folder6,
|
|
MappedPath: filepath.Join(os.TempDir(), folder6),
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: "127.0.0.1:4024",
|
|
Username: baseUser.Username,
|
|
Prefix: path.Join("/", folder6),
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f6, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f7 := vfs.BaseVirtualFolder{
|
|
Name: folder7,
|
|
MappedPath: filepath.Join(os.TempDir(), folder7),
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: sftpServerAddr,
|
|
Username: baseUser.Username,
|
|
Prefix: path.Join("/", folder4),
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f7, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
u := getCryptFsUser()
|
|
u.VirtualFolders = []vfs.VirtualFolder{
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder1,
|
|
},
|
|
VirtualPath: path.Join("/", folder1),
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
},
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder2,
|
|
},
|
|
VirtualPath: path.Join("/", folder2),
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
},
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder3,
|
|
},
|
|
VirtualPath: path.Join("/", folder3),
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
},
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder4,
|
|
},
|
|
VirtualPath: path.Join("/", folder4),
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
},
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder5,
|
|
},
|
|
VirtualPath: path.Join("/", folder5),
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
},
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder6,
|
|
},
|
|
VirtualPath: path.Join("/", folder6),
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
},
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder7,
|
|
},
|
|
VirtualPath: path.Join("/", folder7),
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
},
|
|
}
|
|
|
|
user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
subDir := "testSubDir"
|
|
err = client.Mkdir(subDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(subDir, "afile.bin"), 64, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(subDir, path.Join("/", folder1, subDir))
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join("/", folder1, subDir))
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join("/", folder1, subDir, "afile.bin"))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join("/", folder1, subDir), path.Join("/", folder2, subDir))
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join("/", folder2, subDir))
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join("/", folder2, subDir, "afile.bin"))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join("/", folder2, subDir), path.Join("/", folder3, subDir))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join("/", folder3, "file.bin"), 64, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join("/", folder3, "file.bin"), "/renamed.bin")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join("/", folder3, "file.bin"), path.Join("/", folder2, "/renamed.bin"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join("/", folder3, "file.bin"), path.Join("/", folder3, "/renamed.bin"))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile("/afile.bin", 64, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename("afile.bin", path.Join("/", folder4, "afile_renamed.bin"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join("/", folder4, "afile.bin"), 64, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join("/", folder4, "afile.bin"), path.Join("/", folder5, "afile_renamed.bin"))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join("/", folder5, "afile_renamed.bin"), path.Join("/", folder6, "afile_renamed.bin"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = writeSFTPFile(path.Join("/", folder4, "afile.bin"), 64, client)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join("/", folder7, "afile.bin"))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join("/", folder4, "afile.bin"), path.Join("/", folder7, "afile.bin"))
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(baseUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
for _, folderName := range []string{folder1, folder2, folder3, folder4, folder5, folder6, folder7} {
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(filepath.Join(os.TempDir(), folderName))
|
|
assert.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func TestDirs(t *testing.T) {
|
|
u := getTestUser()
|
|
mappedPath := filepath.Join(os.TempDir(), "vdir")
|
|
folderName := filepath.Base(mappedPath)
|
|
vdirPath := "/path/vdir"
|
|
f := vfs.BaseVirtualFolder{
|
|
Name: folderName,
|
|
MappedPath: mappedPath,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName,
|
|
},
|
|
VirtualPath: vdirPath,
|
|
})
|
|
u.Permissions["/subdir"] = []string{dataprovider.PermDownload, dataprovider.PermUpload,
|
|
dataprovider.PermDelete, dataprovider.PermCreateDirs, dataprovider.PermRename, dataprovider.PermListItems}
|
|
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
info, err := client.ReadDir("/")
|
|
if assert.NoError(t, err) {
|
|
if assert.Len(t, info, 1) {
|
|
assert.Equal(t, "path", info[0].Name())
|
|
}
|
|
}
|
|
fi, err := client.Stat(path.Dir(vdirPath))
|
|
if assert.NoError(t, err) {
|
|
assert.True(t, fi.IsDir())
|
|
}
|
|
err = client.RemoveDirectory("/")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.RemoveDirectory(vdirPath)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.RemoveDirectory(path.Dir(vdirPath))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.Mkdir(vdirPath)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Mkdir("adir")
|
|
assert.NoError(t, err)
|
|
err = client.Rename("/adir", path.Dir(vdirPath))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = client.MkdirAll("/subdir/adir")
|
|
assert.NoError(t, err)
|
|
err = client.Rename("adir", "subdir/adir")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
err = writeSFTPFile("/subdir/afile.bin", 64, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile("/afile.bin", 32, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename("afile.bin", "subdir/afile.bin")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename("afile.bin", "subdir/afile1.bin")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Dir(vdirPath), "renamed_vdir")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCryptFsStat(t *testing.T) {
|
|
user, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testFileSize := int64(4096)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
info, err := client.Stat(testFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, testFileSize, info.Size())
|
|
}
|
|
info, err = os.Stat(filepath.Join(user.HomeDir, testFileName))
|
|
if assert.NoError(t, err) {
|
|
assert.Greater(t, info.Size(), testFileSize)
|
|
}
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestFsPermissionErrors(t *testing.T) {
|
|
if runtime.GOOS == osWindows {
|
|
t.Skip("this test is not available on Windows")
|
|
}
|
|
user, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
testDir := "tDir"
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = os.Chmod(user.GetHomeDir(), 0111)
|
|
assert.NoError(t, err)
|
|
|
|
err = client.RemoveDirectory(testDir)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(testDir, testDir+"1")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
|
|
err = os.Chmod(user.GetHomeDir(), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRenameErrorOutsideHomeDir(t *testing.T) {
|
|
if runtime.GOOS == osWindows {
|
|
t.Skip("this test is not available on Windows")
|
|
}
|
|
oldUploadMode := common.Config.UploadMode
|
|
oldTempPath := common.Config.TempPath
|
|
|
|
common.Config.UploadMode = common.UploadModeAtomicWithResume
|
|
common.Config.TempPath = filepath.Clean(os.TempDir())
|
|
vfs.SetTempPath(common.Config.TempPath)
|
|
|
|
u := getTestUser()
|
|
u.QuotaFiles = 1000
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = os.Chmod(user.GetHomeDir(), 0555)
|
|
assert.NoError(t, err)
|
|
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), user.UsedQuotaSize)
|
|
|
|
err = os.Chmod(user.GetHomeDir(), os.ModeDir)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
common.Config.UploadMode = oldUploadMode
|
|
common.Config.TempPath = oldTempPath
|
|
vfs.SetTempPath(oldTempPath)
|
|
}
|
|
|
|
func TestResolvePathError(t *testing.T) {
|
|
u := getTestUser()
|
|
u.HomeDir = "relative_path"
|
|
conn := common.NewBaseConnection("", common.ProtocolFTP, "", "", u)
|
|
testPath := "apath"
|
|
_, err := conn.ListDir(testPath)
|
|
assert.Error(t, err)
|
|
err = conn.CreateDir(testPath, true)
|
|
assert.Error(t, err)
|
|
err = conn.RemoveDir(testPath)
|
|
assert.Error(t, err)
|
|
err = conn.Rename(testPath, testPath+"1")
|
|
assert.Error(t, err)
|
|
err = conn.CreateSymlink(testPath, testPath+".sym")
|
|
assert.Error(t, err)
|
|
_, err = conn.DoStat(testPath, 0, false)
|
|
assert.Error(t, err)
|
|
err = conn.RemoveAll(testPath)
|
|
assert.Error(t, err)
|
|
err = conn.SetStat(testPath, &common.StatAttributes{
|
|
Atime: time.Now(),
|
|
Mtime: time.Now(),
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
u = getTestUser()
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
MappedPath: "relative_mapped_path",
|
|
},
|
|
VirtualPath: "/vpath",
|
|
})
|
|
err = os.MkdirAll(u.HomeDir, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
conn.User = u
|
|
err = conn.Rename(testPath, "/vpath/subpath")
|
|
assert.Error(t, err)
|
|
|
|
outHomePath := filepath.Join(os.TempDir(), testFileName)
|
|
err = os.WriteFile(outHomePath, testFileContent, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.Symlink(outHomePath, filepath.Join(u.HomeDir, testFileName+".link"))
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(u.HomeDir, testFileName), testFileContent, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = conn.CreateSymlink(testFileName, testFileName+".link")
|
|
assert.Error(t, err)
|
|
|
|
err = os.RemoveAll(u.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.Remove(outHomePath)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestUserPasswordHashing(t *testing.T) {
|
|
if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
|
|
t.Skip("this test is not supported with the memory provider")
|
|
}
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf := config.GetProviderConf()
|
|
providerConf.PasswordHashing.Algo = dataprovider.HashingAlgoArgon2ID
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
|
|
currentUser, err := dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
assert.True(t, strings.HasPrefix(currentUser.Password, "$2a$"))
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
u = getTestUser()
|
|
user, _, err = httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
currentUser, err = dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
assert.True(t, strings.HasPrefix(currentUser.Password, "$argon2id$"))
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf = config.GetProviderConf()
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestAllowList(t *testing.T) {
|
|
configCopy := common.Config
|
|
|
|
entries := []dataprovider.IPListEntry{
|
|
{
|
|
IPOrNet: "172.18.1.1/32",
|
|
Type: dataprovider.IPListTypeAllowList,
|
|
Mode: dataprovider.ListModeAllow,
|
|
Protocols: 0,
|
|
},
|
|
{
|
|
IPOrNet: "172.18.1.2/32",
|
|
Type: dataprovider.IPListTypeAllowList,
|
|
Mode: dataprovider.ListModeAllow,
|
|
Protocols: 0,
|
|
},
|
|
{
|
|
IPOrNet: "10.8.7.0/24",
|
|
Type: dataprovider.IPListTypeAllowList,
|
|
Mode: dataprovider.ListModeAllow,
|
|
Protocols: 5,
|
|
},
|
|
{
|
|
IPOrNet: "0.0.0.0/0",
|
|
Type: dataprovider.IPListTypeAllowList,
|
|
Mode: dataprovider.ListModeAllow,
|
|
Protocols: 8,
|
|
},
|
|
{
|
|
IPOrNet: "::/0",
|
|
Type: dataprovider.IPListTypeAllowList,
|
|
Mode: dataprovider.ListModeAllow,
|
|
Protocols: 8,
|
|
},
|
|
}
|
|
|
|
for _, e := range entries {
|
|
_, resp, err := httpdtest.AddIPListEntry(e, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
}
|
|
|
|
common.Config.AllowListStatus = 1
|
|
err := common.Initialize(common.Config, 0)
|
|
assert.NoError(t, err)
|
|
assert.True(t, common.Config.IsAllowListEnabled())
|
|
|
|
testIP := "172.18.1.1"
|
|
assert.NoError(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolFTP))
|
|
entry := entries[0]
|
|
entry.Protocols = 1
|
|
_, _, err = httpdtest.UpdateIPListEntry(entry, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Error(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolFTP))
|
|
assert.NoError(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolSSH))
|
|
_, err = httpdtest.RemoveIPListEntry(entry, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
entries = entries[1:]
|
|
assert.Error(t, common.Connections.IsNewConnectionAllowed(testIP, common.ProtocolSSH))
|
|
assert.Error(t, common.Connections.IsNewConnectionAllowed("172.18.1.3", common.ProtocolSSH))
|
|
assert.NoError(t, common.Connections.IsNewConnectionAllowed("172.18.1.3", common.ProtocolHTTP))
|
|
|
|
assert.NoError(t, common.Connections.IsNewConnectionAllowed("10.8.7.3", common.ProtocolWebDAV))
|
|
assert.NoError(t, common.Connections.IsNewConnectionAllowed("10.8.7.4", common.ProtocolSSH))
|
|
assert.Error(t, common.Connections.IsNewConnectionAllowed("10.8.7.4", common.ProtocolFTP))
|
|
assert.NoError(t, common.Connections.IsNewConnectionAllowed("10.8.7.4", common.ProtocolHTTP))
|
|
assert.NoError(t, common.Connections.IsNewConnectionAllowed("2001:0db8::1428:57ab", common.ProtocolHTTP))
|
|
assert.Error(t, common.Connections.IsNewConnectionAllowed("2001:0db8::1428:57ab", common.ProtocolSSH))
|
|
assert.Error(t, common.Connections.IsNewConnectionAllowed("10.8.8.2", common.ProtocolWebDAV))
|
|
assert.Error(t, common.Connections.IsNewConnectionAllowed("invalid IP", common.ProtocolHTTP))
|
|
|
|
common.Config = configCopy
|
|
err = common.Initialize(common.Config, 0)
|
|
assert.NoError(t, err)
|
|
assert.False(t, common.Config.IsAllowListEnabled())
|
|
|
|
for _, e := range entries {
|
|
_, err := httpdtest.RemoveIPListEntry(e, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func TestDbDefenderErrors(t *testing.T) {
|
|
if !isDbDefenderSupported() {
|
|
t.Skip("this test is not supported with the current database provider")
|
|
}
|
|
configCopy := common.Config
|
|
common.Config.DefenderConfig.Enabled = true
|
|
common.Config.DefenderConfig.Driver = common.DefenderDriverProvider
|
|
err := common.Initialize(common.Config, 0)
|
|
assert.NoError(t, err)
|
|
|
|
testIP := "127.1.1.1"
|
|
hosts, err := common.GetDefenderHosts()
|
|
assert.NoError(t, err)
|
|
assert.Len(t, hosts, 0)
|
|
common.AddDefenderEvent(testIP, common.ProtocolSSH, common.HostEventLimitExceeded)
|
|
hosts, err = common.GetDefenderHosts()
|
|
assert.NoError(t, err)
|
|
assert.Len(t, hosts, 1)
|
|
score, err := common.GetDefenderScore(testIP)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3, score)
|
|
banTime, err := common.GetDefenderBanTime(testIP)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, banTime)
|
|
|
|
err = dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
|
|
common.AddDefenderEvent(testIP, common.ProtocolFTP, common.HostEventLimitExceeded)
|
|
_, err = common.GetDefenderHosts()
|
|
assert.Error(t, err)
|
|
_, err = common.GetDefenderHost(testIP)
|
|
assert.Error(t, err)
|
|
_, err = common.GetDefenderBanTime(testIP)
|
|
assert.Error(t, err)
|
|
_, err = common.GetDefenderScore(testIP)
|
|
assert.Error(t, err)
|
|
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf := config.GetProviderConf()
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.CleanupDefender(util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Hour)))
|
|
assert.NoError(t, err)
|
|
|
|
common.Config = configCopy
|
|
err = common.Initialize(common.Config, 0)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestDelayedQuotaUpdater(t *testing.T) {
|
|
err := dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf := config.GetProviderConf()
|
|
providerConf.DelayedQuotaUpdate = 120
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
|
|
u := getTestUser()
|
|
u.QuotaFiles = 100
|
|
u.TotalDataTransfer = 2000
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.UpdateUserQuota(&user, 10, 6000, false)
|
|
assert.NoError(t, err)
|
|
err = dataprovider.UpdateUserTransferQuota(&user, 100, 200, false)
|
|
assert.NoError(t, err)
|
|
files, size, ulSize, dlSize, err := dataprovider.GetUsedQuota(user.Username)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 10, files)
|
|
assert.Equal(t, int64(6000), size)
|
|
assert.Equal(t, int64(100), ulSize)
|
|
assert.Equal(t, int64(200), dlSize)
|
|
|
|
userGet, err := dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, userGet.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), userGet.UsedQuotaSize)
|
|
assert.Equal(t, int64(0), userGet.UsedUploadDataTransfer)
|
|
assert.Equal(t, int64(0), userGet.UsedDownloadDataTransfer)
|
|
|
|
err = dataprovider.UpdateUserQuota(&user, 10, 6000, true)
|
|
assert.NoError(t, err)
|
|
err = dataprovider.UpdateUserTransferQuota(&user, 100, 200, true)
|
|
assert.NoError(t, err)
|
|
files, size, ulSize, dlSize, err = dataprovider.GetUsedQuota(user.Username)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 10, files)
|
|
assert.Equal(t, int64(6000), size)
|
|
assert.Equal(t, int64(100), ulSize)
|
|
assert.Equal(t, int64(200), dlSize)
|
|
|
|
userGet, err = dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 10, userGet.UsedQuotaFiles)
|
|
assert.Equal(t, int64(6000), userGet.UsedQuotaSize)
|
|
assert.Equal(t, int64(100), userGet.UsedUploadDataTransfer)
|
|
assert.Equal(t, int64(200), userGet.UsedDownloadDataTransfer)
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
folder := vfs.BaseVirtualFolder{
|
|
Name: "folder",
|
|
MappedPath: filepath.Join(os.TempDir(), "p"),
|
|
}
|
|
err = dataprovider.AddFolder(&folder, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.UpdateVirtualFolderQuota(&folder, 10, 6000, false)
|
|
assert.NoError(t, err)
|
|
files, size, err = dataprovider.GetUsedVirtualFolderQuota(folder.Name)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 10, files)
|
|
assert.Equal(t, int64(6000), size)
|
|
|
|
folderGet, err := dataprovider.GetFolderByName(folder.Name)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, folderGet.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), folderGet.UsedQuotaSize)
|
|
|
|
err = dataprovider.UpdateVirtualFolderQuota(&folder, 10, 6000, true)
|
|
assert.NoError(t, err)
|
|
files, size, err = dataprovider.GetUsedVirtualFolderQuota(folder.Name)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 10, files)
|
|
assert.Equal(t, int64(6000), size)
|
|
|
|
folderGet, err = dataprovider.GetFolderByName(folder.Name)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 10, folderGet.UsedQuotaFiles)
|
|
assert.Equal(t, int64(6000), folderGet.UsedQuotaSize)
|
|
|
|
err = dataprovider.DeleteFolder(folder.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf = config.GetProviderConf()
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPasswordCaching(t *testing.T) {
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
dbUser, err := dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
found, match := dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.False(t, found)
|
|
assert.False(t, match)
|
|
|
|
user.Password = "wrong"
|
|
_, _, err = getSftpClient(user)
|
|
assert.Error(t, err)
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.False(t, found)
|
|
assert.False(t, match)
|
|
user.Password = ""
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.True(t, found)
|
|
assert.True(t, match)
|
|
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword+"_", dbUser.Password)
|
|
assert.True(t, found)
|
|
assert.False(t, match)
|
|
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username+"_", defaultPassword, dbUser.Password)
|
|
assert.False(t, found)
|
|
assert.False(t, match)
|
|
|
|
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
// the password was not changed
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.True(t, found)
|
|
assert.True(t, match)
|
|
// the password hash will change
|
|
user.Password = defaultPassword
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
dbUser, err = dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.False(t, found)
|
|
assert.False(t, match)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.True(t, found)
|
|
assert.True(t, match)
|
|
//change password
|
|
newPassword := defaultPassword + "mod"
|
|
user.Password = newPassword
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
dbUser, err = dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, newPassword, dbUser.Password)
|
|
assert.False(t, found)
|
|
assert.False(t, match)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.True(t, found)
|
|
assert.False(t, match)
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, newPassword, dbUser.Password)
|
|
assert.True(t, found)
|
|
assert.True(t, match)
|
|
// update the password
|
|
err = dataprovider.UpdateUserPassword(user.Username, defaultPassword, "", "", "")
|
|
assert.NoError(t, err)
|
|
dbUser, err = dataprovider.UserExists(user.Username, "")
|
|
assert.NoError(t, err)
|
|
// the stored hash does not match
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.False(t, found)
|
|
assert.False(t, match)
|
|
|
|
user.Password = defaultPassword
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = checkBasicSFTP(client)
|
|
assert.NoError(t, err)
|
|
}
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.True(t, found)
|
|
assert.True(t, match)
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
found, match = dataprovider.CheckCachedUserPassword(user.Username, defaultPassword, dbUser.Password)
|
|
assert.False(t, found)
|
|
assert.False(t, match)
|
|
}
|
|
|
|
func TestEventRule(t *testing.T) {
|
|
if runtime.GOOS == osWindows {
|
|
t.Skip("this test is not available on Windows")
|
|
}
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeHTTP,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
HTTPConfig: dataprovider.EventActionHTTPConfig{
|
|
Endpoint: "http://localhost",
|
|
Timeout: 20,
|
|
Method: http.MethodGet,
|
|
},
|
|
},
|
|
}
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "action2",
|
|
Type: dataprovider.ActionTypeBackup,
|
|
}
|
|
a3 := dataprovider.BaseEventAction{
|
|
Name: "action3",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test1@example.com", "test2@example.com"},
|
|
Bcc: []string{"test3@example.com"},
|
|
Subject: `New "{{Event}}" from "{{Name}}" status {{StatusString}}`,
|
|
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} Data: {{ObjectData}} {{ErrorString}}",
|
|
},
|
|
},
|
|
}
|
|
a4 := dataprovider.BaseEventAction{
|
|
Name: "action4",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"failure@example.com"},
|
|
Subject: `Failed "{{Event}}" from "{{Name}}"`,
|
|
Body: "Fs path {{FsPath}}, protocol: {{Protocol}}, IP: {{IP}} {{ErrorString}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
action4, _, err := httpdtest.AddEventAction(a4, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
Options: dataprovider.ConditionOptions{
|
|
EventStatuses: []int{1},
|
|
FsPaths: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "/subdir/*.dat",
|
|
},
|
|
{
|
|
Pattern: "/**/*.txt",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: true,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 3,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action4.Name,
|
|
},
|
|
Order: 4,
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r2 := dataprovider.EventRule{
|
|
Name: "test rule2",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"download"},
|
|
Options: dataprovider.ConditionOptions{
|
|
FsPaths: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "/**/*.dat",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action4.Name,
|
|
},
|
|
Order: 2,
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r3 := dataprovider.EventRule{
|
|
Name: "test rule3",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerProviderEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
ProviderEvents: []string{"delete"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule3, _, err := httpdtest.AddEventRule(r3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
|
|
u := getTestUser()
|
|
u.DownloadDataTransfer = 1
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
movedFileName := "moved.dat"
|
|
movedPath := filepath.Join(user.HomeDir, movedFileName)
|
|
err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 0), 0755)
|
|
assert.NoError(t, err)
|
|
|
|
action1.Type = dataprovider.ActionTypeCommand
|
|
action1.Options = dataprovider.BaseEventActionOptions{
|
|
CmdConfig: dataprovider.EventActionCommandConfig{
|
|
Cmd: uploadScriptPath,
|
|
Timeout: 10,
|
|
EnvVars: []dataprovider.KeyValue{
|
|
{
|
|
Key: "SFTPGO_ACTION_PATH",
|
|
Value: "{{FsPath}}",
|
|
},
|
|
{
|
|
Key: "CUSTOM_ENV_VAR",
|
|
Value: "value",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
dirName := "subdir"
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
size := int64(32768)
|
|
// rule conditions does not match
|
|
err = writeSFTPFileNoCheck(testFileName, size, client)
|
|
assert.NoError(t, err)
|
|
info, err := client.Stat(testFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, size, info.Size())
|
|
}
|
|
err = client.Mkdir(dirName)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir("subdir1")
|
|
assert.NoError(t, err)
|
|
// rule conditions match
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), size, client)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join(dirName, testFileName))
|
|
assert.Error(t, err)
|
|
info, err = client.Stat(movedFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, size, info.Size())
|
|
}
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 3)
|
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "upload" from "%s" status OK`, user.Username))
|
|
// test the failure action, we download a file that exceeds the transfer quota limit
|
|
err = writeSFTPFileNoCheck(path.Join("subdir1", testFileName), 1*1024*1024+65535, client)
|
|
assert.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
f, err := client.Open(path.Join("subdir1", testFileName))
|
|
assert.NoError(t, err)
|
|
_, err = io.ReadAll(f)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
|
|
}
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 3)
|
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s" status KO`, user.Username))
|
|
assert.Contains(t, email.Data, `"download" failed`)
|
|
assert.Contains(t, email.Data, common.ErrReadQuotaExceeded.Error())
|
|
_, err = httpdtest.UpdateTransferQuotaUsage(user, "", http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
// remove the upload script to test the failure action
|
|
err = os.Remove(uploadScriptPath)
|
|
assert.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), size, client)
|
|
assert.Error(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: Failed "upload" from "%s"`, user.Username))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`action %q failed`, action1.Name))
|
|
// now test the download rule
|
|
lastReceivedEmail.reset()
|
|
f, err = client.Open(movedFileName)
|
|
assert.NoError(t, err)
|
|
contents, err := io.ReadAll(f)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
assert.Len(t, contents, int(size))
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 3)
|
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s"`, user.Username))
|
|
}
|
|
// test upload action command with arguments
|
|
action1.Options.CmdConfig.Args = []string{"{{Event}}", "{{VirtualPath}}", "custom_arg"}
|
|
action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
uploadLogFilePath := filepath.Join(os.TempDir(), "upload.log")
|
|
err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, uploadLogFilePath, 0), 0755)
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = writeSFTPFileNoCheck(path.Join(dirName, testFileName), 123, client)
|
|
assert.NoError(t, err)
|
|
|
|
logContent, err := os.ReadFile(uploadLogFilePath)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, fmt.Sprintf("upload %s custom_arg", util.CleanPath(path.Join(dirName, testFileName))),
|
|
strings.TrimSpace(string(logContent)))
|
|
|
|
err = os.Remove(uploadLogFilePath)
|
|
assert.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
}
|
|
|
|
lastReceivedEmail.reset()
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 3)
|
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: New "delete" from "admin"`)
|
|
_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleStatues(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test6@example.com"},
|
|
Subject: `New "{{Event}}" error`,
|
|
Body: "{{ErrorString}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r := dataprovider.EventRule{
|
|
Name: "rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
Options: dataprovider.ConditionOptions{
|
|
EventStatuses: []int{3},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule, resp, err := httpdtest.AddEventRule(r, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
u := getTestUser()
|
|
u.UploadDataTransfer = 1
|
|
u.DownloadDataTransfer = 1
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
testFileSize := int64(999999)
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
f, err := client.Open(testFileName)
|
|
assert.NoError(t, err)
|
|
contents := make([]byte, testFileSize)
|
|
n, err := io.ReadFull(f, contents)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int(testFileSize), n)
|
|
assert.Len(t, contents, int(testFileSize))
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
|
|
lastReceivedEmail.reset()
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From == ""
|
|
}, 600*time.Millisecond, 500*time.Millisecond)
|
|
|
|
err = writeSFTPFile(testFileName, testFileSize, client)
|
|
assert.Error(t, err)
|
|
lastReceivedEmail.reset()
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test6@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: New "upload" error`)
|
|
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleProviderEvents(t *testing.T) {
|
|
if runtime.GOOS == osWindows {
|
|
t.Skip("this test is not available on Windows")
|
|
}
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
saveObjectScriptPath := filepath.Join(os.TempDir(), "provider.sh")
|
|
outPath := filepath.Join(os.TempDir(), "provider_out.json")
|
|
err = os.WriteFile(saveObjectScriptPath, getSaveProviderObjectScriptContent(outPath, 0), 0755)
|
|
assert.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeCommand,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
CmdConfig: dataprovider.EventActionCommandConfig{
|
|
Cmd: saveObjectScriptPath,
|
|
Timeout: 10,
|
|
EnvVars: []dataprovider.KeyValue{
|
|
{
|
|
Key: "SFTPGO_OBJECT_DATA",
|
|
Value: "{{ObjectData}}",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test3@example.com"},
|
|
Subject: `New "{{Event}}" from "{{Name}}"`,
|
|
Body: "Object name: {{ObjectName}} object type: {{ObjectType}} Data: {{ObjectData}}",
|
|
},
|
|
},
|
|
}
|
|
|
|
a3 := dataprovider.BaseEventAction{
|
|
Name: "a3",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"failure@example.com"},
|
|
Subject: `Failed "{{Event}}" from "{{Name}}"`,
|
|
Body: "Object name: {{ObjectName}} object type: {{ObjectType}}, IP: {{IP}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r := dataprovider.EventRule{
|
|
Name: "rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerProviderEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
ProviderEvents: []string{"update"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
StopOnFailure: true,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 3,
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
StopOnFailure: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule, _, err := httpdtest.AddEventRule(r, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
lastReceivedEmail.reset()
|
|
// create and update a folder to trigger the rule
|
|
folder := vfs.BaseVirtualFolder{
|
|
Name: "ftest rule",
|
|
MappedPath: filepath.Join(os.TempDir(), "p"),
|
|
}
|
|
folder, _, err = httpdtest.AddFolder(folder, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
// no action is triggered on add
|
|
assert.NoFileExists(t, outPath)
|
|
// update the folder
|
|
_, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
if assert.Eventually(t, func() bool {
|
|
_, err := os.Stat(outPath)
|
|
return err == nil
|
|
}, 2*time.Second, 100*time.Millisecond) {
|
|
content, err := os.ReadFile(outPath)
|
|
assert.NoError(t, err)
|
|
var folderGet vfs.BaseVirtualFolder
|
|
err = json.Unmarshal(content, &folderGet)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, folder, folderGet)
|
|
err = os.Remove(outPath)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: New "update" from "admin"`)
|
|
}
|
|
// now delete the script to generate an error
|
|
lastReceivedEmail.reset()
|
|
err = os.Remove(saveObjectScriptPath)
|
|
assert.NoError(t, err)
|
|
_, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.NoFileExists(t, outPath)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: Failed "update" from "admin"`)
|
|
assert.Contains(t, email.Data, fmt.Sprintf("Object name: %s object type: folder", folder.Name))
|
|
lastReceivedEmail.reset()
|
|
// generate an error for the failure action
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
_, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.NoFileExists(t, outPath)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 0)
|
|
|
|
_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleFsActions(t *testing.T) {
|
|
dirsToCreate := []string{
|
|
"/basedir/1",
|
|
"/basedir/sub/2",
|
|
"/basedir/3",
|
|
}
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionMkdirs,
|
|
MkDirs: dirsToCreate,
|
|
},
|
|
},
|
|
}
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionRename,
|
|
Renames: []dataprovider.RenameConfig{
|
|
{
|
|
KeyValue: dataprovider.KeyValue{
|
|
Key: "/{{VirtualDirPath}}/{{ObjectName}}",
|
|
Value: "/{{ObjectName}}_renamed",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
a3 := dataprovider.BaseEventAction{
|
|
Name: "a3",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionDelete,
|
|
Deletes: []string{"/{{ObjectName}}_renamed"},
|
|
},
|
|
},
|
|
}
|
|
a4 := dataprovider.BaseEventAction{
|
|
Name: "a4",
|
|
Type: dataprovider.ActionTypeFolderQuotaReset,
|
|
}
|
|
a5 := dataprovider.BaseEventAction{
|
|
Name: "a5",
|
|
Type: dataprovider.ActionTypeUserQuotaReset,
|
|
}
|
|
a6 := dataprovider.BaseEventAction{
|
|
Name: "a6",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionExist,
|
|
Exist: []string{"/{{VirtualPath}}"},
|
|
},
|
|
},
|
|
}
|
|
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
action2, resp, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
action3, resp, err := httpdtest.AddEventAction(a3, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
action4, resp, err := httpdtest.AddEventAction(a4, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
action5, resp, err := httpdtest.AddEventAction(a5, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
action6, resp, err := httpdtest.AddEventAction(a6, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "r1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerProviderEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
ProviderEvents: []string{"add"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
r2 := dataprovider.EventRule{
|
|
Name: "r2",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action5.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
r3 := dataprovider.EventRule{
|
|
Name: "r3",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"mkdir"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action6.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
r4 := dataprovider.EventRule{
|
|
Name: "r4",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"rmdir"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action4.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
r5 := dataprovider.EventRule{
|
|
Name: "r5",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerProviderEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
ProviderEvents: []string{"add"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action4.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
rule3, _, err := httpdtest.AddEventRule(r3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
rule4, _, err := httpdtest.AddEventRule(r4, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
rule5, _, err := httpdtest.AddEventRule(r5, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
folderMappedPath := filepath.Join(os.TempDir(), "folder")
|
|
err = os.MkdirAll(folderMappedPath, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
err = os.WriteFile(filepath.Join(folderMappedPath, "file.txt"), []byte("1"), 0666)
|
|
assert.NoError(t, err)
|
|
|
|
folder, _, err := httpdtest.AddFolder(vfs.BaseVirtualFolder{
|
|
Name: "test folder",
|
|
MappedPath: folderMappedPath,
|
|
}, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
folderGet, _, err := httpdtest.GetFolderByName(folder.Name, http.StatusOK)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return folderGet.UsedQuotaFiles == 1 && folderGet.UsedQuotaSize == 1
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
u := getTestUser()
|
|
u.Filters.DisableFsChecks = true
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
// check initial directories creation
|
|
for _, dir := range dirsToCreate {
|
|
assert.Eventually(t, func() bool {
|
|
_, err := client.Stat(dir)
|
|
return err == nil
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
}
|
|
// upload a file and check the sync rename
|
|
size := int64(32768)
|
|
err = writeSFTPFileNoCheck(path.Join("basedir", testFileName), size, client)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join("basedir", testFileName))
|
|
assert.Error(t, err)
|
|
info, err := client.Stat(testFileName + "_renamed") //nolint:goconst
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, size, info.Size())
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
userGet, _, err := httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return userGet.UsedQuotaFiles == 1 && userGet.UsedQuotaSize == size
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
err = client.Mkdir(testFileName)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
_, err = client.Stat(testFileName + "_renamed")
|
|
return err != nil
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
err = client.RemoveDirectory(testFileName)
|
|
assert.NoError(t, err)
|
|
}
|
|
err = client.Mkdir(testFileName + "_renamed")
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(testFileName)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
_, err = client.Stat(testFileName + "_renamed")
|
|
return err != nil
|
|
}, 2*time.Second, 100*time.Millisecond)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(folderMappedPath)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule4, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule5, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action5, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action6, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestUploadEventRule(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test1@example.com"},
|
|
Subject: `New "{{Event}}" from "{{Name}}" status {{StatusString}}`,
|
|
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} Data: {{ObjectData}} {{ErrorString}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
Options: dataprovider.ConditionOptions{
|
|
FsPaths: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "/**/*.filepart",
|
|
InverseMatch: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFileNoCheck("/test.filepart", 32768, client)
|
|
assert.NoError(t, err)
|
|
email := lastReceivedEmail.get()
|
|
assert.Empty(t, email.From)
|
|
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFileNoCheck(testFileName, 32768, client)
|
|
assert.NoError(t, err)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.Contains(t, email.Data, `Subject: New "upload"`)
|
|
}
|
|
|
|
r2 := dataprovider.EventRule{
|
|
Name: "test rule2",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"rename"},
|
|
Options: dataprovider.ConditionOptions{
|
|
FsPaths: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "/**/*.filepart",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
tempName := "file.filepart"
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFileNoCheck(tempName, 32768, client)
|
|
assert.NoError(t, err)
|
|
email := lastReceivedEmail.get()
|
|
assert.Empty(t, email.From)
|
|
|
|
lastReceivedEmail.reset()
|
|
err = client.Rename(tempName, testFileName)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.Contains(t, email.Data, `Subject: New "rename"`)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRulePreDelete(t *testing.T) {
|
|
movePath := "recycle bin"
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionRename,
|
|
Renames: []dataprovider.RenameConfig{
|
|
{
|
|
KeyValue: dataprovider.KeyValue{
|
|
Key: "/{{VirtualPath}}",
|
|
Value: fmt.Sprintf("/%s/{{VirtualPath}}", movePath),
|
|
},
|
|
UpdateModTime: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"pre-delete"},
|
|
Options: dataprovider.ConditionOptions{
|
|
FsPaths: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: fmt.Sprintf("/%s/**", movePath),
|
|
InverseMatch: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f := vfs.BaseVirtualFolder{
|
|
Name: movePath,
|
|
MappedPath: filepath.Join(os.TempDir(), movePath),
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getTestUser()
|
|
u.QuotaFiles = 1000
|
|
u.VirtualFolders = []vfs.VirtualFolder{
|
|
{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: movePath,
|
|
},
|
|
VirtualPath: "/" + movePath,
|
|
QuotaFiles: 1000,
|
|
},
|
|
}
|
|
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u = getTestSFTPUser()
|
|
u.QuotaFiles = 1000
|
|
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
for _, user := range []dataprovider.User{localUser, sftpUser} {
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
testDir := "sub dir"
|
|
err = client.MkdirAll(testDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, 100, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, testFileName), 100, client)
|
|
assert.NoError(t, err)
|
|
modTime := time.Now().Add(-36 * time.Hour)
|
|
err = client.Chtimes(testFileName, modTime, modTime)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(testFileName)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(path.Join(testDir, testFileName))
|
|
assert.NoError(t, err)
|
|
// check files
|
|
_, err = client.Stat(testFileName)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
_, err = client.Stat(path.Join(testDir, testFileName))
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
info, err := client.Stat(path.Join("/", movePath, testFileName))
|
|
assert.NoError(t, err)
|
|
diff := math.Abs(time.Until(info.ModTime()).Seconds())
|
|
assert.LessOrEqual(t, diff, float64(2))
|
|
|
|
_, err = client.Stat(path.Join("/", movePath, testDir, testFileName))
|
|
assert.NoError(t, err)
|
|
// check quota
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
if user.Username == localUser.Username {
|
|
assert.Equal(t, 0, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), user.UsedQuotaSize)
|
|
folder, _, err := httpdtest.GetFolderByName(movePath, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, folder.UsedQuotaFiles)
|
|
assert.Equal(t, int64(200), folder.UsedQuotaSize)
|
|
} else {
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, int64(100), user.UsedQuotaSize)
|
|
}
|
|
// pre-delete action is not executed in movePath
|
|
err = client.Remove(path.Join("/", movePath, testFileName))
|
|
assert.NoError(t, err)
|
|
if user.Username == localUser.Username {
|
|
// check quota
|
|
folder, _, err := httpdtest.GetFolderByName(movePath, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, folder.UsedQuotaFiles)
|
|
assert.Equal(t, int64(100), folder.UsedQuotaSize)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(localUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: movePath}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(filepath.Join(os.TempDir(), movePath))
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRulePreDownloadUpload(t *testing.T) {
|
|
testDir := "/d"
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionMkdirs,
|
|
MkDirs: []string{testDir},
|
|
},
|
|
},
|
|
}
|
|
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionRename,
|
|
Renames: []dataprovider.RenameConfig{
|
|
{
|
|
KeyValue: dataprovider.KeyValue{
|
|
Key: "/missing source",
|
|
Value: "/missing target",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action2, resp, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"pre-download", "pre-upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
// the rule will always succeed, so uploads/downloads will work
|
|
err = writeSFTPFile(testFileName, 100, client)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory(testDir)
|
|
assert.NoError(t, err)
|
|
f, err := client.Open(testFileName)
|
|
assert.NoError(t, err)
|
|
contents := make([]byte, 100)
|
|
n, err := io.ReadFull(f, contents)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int(100), n)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
// disable the rule
|
|
rule1.Status = 0
|
|
_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(testFileName)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, 100, client)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(testDir)
|
|
assert.ErrorIs(t, err, fs.ErrNotExist)
|
|
// now update the rule so that it will always fail
|
|
rule1.Status = 1
|
|
rule1.Actions = []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = client.Open(testFileName)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Remove(testFileName)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, 100, client)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionCommandEnvVars(t *testing.T) {
|
|
if runtime.GOOS == osWindows {
|
|
t.Skip("this test is not available on Windows")
|
|
}
|
|
envName := "MY_ENV"
|
|
uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
|
|
|
|
err := os.WriteFile(uploadScriptPath, getUploadScriptEnvContent(envName), 0755)
|
|
assert.NoError(t, err)
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeCommand,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
CmdConfig: dataprovider.EventActionCommandConfig{
|
|
Cmd: uploadScriptPath,
|
|
Timeout: 10,
|
|
EnvVars: []dataprovider.KeyValue{
|
|
{
|
|
Key: envName,
|
|
Value: "$",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = writeSFTPFileNoCheck(testFileName, 100, client)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
os.Setenv(envName, "1")
|
|
defer os.Unsetenv(envName)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = writeSFTPFileNoCheck(testFileName, 100, client)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.Remove(uploadScriptPath)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestFsActionCopy(t *testing.T) {
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionCopy,
|
|
Copy: []dataprovider.KeyValue{
|
|
{
|
|
Key: "/{{VirtualPath}}/",
|
|
Value: "/dircopy/",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = writeSFTPFile(testFileName, 100, client)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(path.Join("dircopy", testFileName))
|
|
assert.NoError(t, err)
|
|
|
|
action1.Options.FsConfig.Copy = []dataprovider.KeyValue{
|
|
{
|
|
Key: "/missing path",
|
|
Value: "/copied path",
|
|
},
|
|
}
|
|
_, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
// copy a missing path will fail
|
|
err = writeSFTPFile(testFileName, 100, client)
|
|
assert.Error(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventFsActionsGroupFilters(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"example@example.net"},
|
|
Subject: `New "{{Event}}" from "{{Name}}" status {{StatusString}}`,
|
|
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} {{ErrorString}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
Options: dataprovider.ConditionOptions{
|
|
GroupNames: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: "group*",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
// the user has no group, so the rule does not match
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFile(testFileName, 32, client)
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, lastReceivedEmail.get().From)
|
|
}
|
|
g1 := dataprovider.Group{
|
|
BaseGroup: sdk.BaseGroup{
|
|
Name: "agroup1",
|
|
},
|
|
}
|
|
group1, _, err := httpdtest.AddGroup(g1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
g2 := dataprovider.Group{
|
|
BaseGroup: sdk.BaseGroup{
|
|
Name: "group2",
|
|
},
|
|
}
|
|
group2, _, err := httpdtest.AddGroup(g2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
user.Groups = []sdk.GroupMapping{
|
|
{
|
|
Name: group1.Name,
|
|
Type: sdk.GroupTypePrimary,
|
|
},
|
|
}
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
// the group does not match
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFile(testFileName, 32, client)
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, lastReceivedEmail.get().From)
|
|
}
|
|
user.Groups = append(user.Groups, sdk.GroupMapping{
|
|
Name: group2.Name,
|
|
Type: sdk.GroupTypeSecondary,
|
|
})
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
// the group matches
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFile(testFileName, 32, client)
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, lastReceivedEmail.get().From)
|
|
}
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveGroup(group1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveGroup(group2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestBackupAsAttachment(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeBackup,
|
|
}
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"{{Event}} {{StatusString}}"`,
|
|
Body: "Domain: {{Name}}",
|
|
Attachments: []string{"/{{VirtualPath}}"},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule certificate",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerCertificate,
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
lastReceivedEmail.reset()
|
|
renewalEvent := "Certificate renewal"
|
|
|
|
common.HandleCertificateEvent(common.EventParams{
|
|
Name: "example.com",
|
|
Timestamp: time.Now(),
|
|
Status: 1,
|
|
Event: renewalEvent,
|
|
})
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
|
|
assert.Contains(t, email.Data, `Domain: example.com`)
|
|
assert.Contains(t, email.Data, "Content-Type: application/json")
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionHTTPMultipart(t *testing.T) {
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeHTTP,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
HTTPConfig: dataprovider.EventActionHTTPConfig{
|
|
Endpoint: fmt.Sprintf("http://%s/multipart", httpAddr),
|
|
Method: http.MethodPut,
|
|
Parts: []dataprovider.HTTPPart{
|
|
{
|
|
Name: "part1",
|
|
Headers: []dataprovider.KeyValue{
|
|
{
|
|
Key: "Content-Type",
|
|
Value: "application/json",
|
|
},
|
|
},
|
|
Body: `{"FilePath": "{{VirtualPath}}"}`,
|
|
},
|
|
{
|
|
Name: "file",
|
|
Filepath: "/{{VirtualPath}}",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, resp, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test http multipart",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
// now add an missing file to the http multipart action
|
|
action1.Options.HTTPConfig.Parts = append(action1.Options.HTTPConfig.Parts, dataprovider.HTTPPart{
|
|
Name: "file1",
|
|
Filepath: "/missing",
|
|
})
|
|
_, resp, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
f, err = client.Create("testfile.txt")
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionCompress(t *testing.T) {
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionCompress,
|
|
Compress: dataprovider.EventActionFsCompress{
|
|
Name: "/{{VirtualPath}}.zip",
|
|
Paths: []string{"/{{VirtualPath}}"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test compress",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
u := getTestUser()
|
|
u.QuotaFiles = 1000
|
|
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u = getTestSFTPUser()
|
|
u.FsConfig.SFTPConfig.BufferSize = 1
|
|
u.QuotaFiles = 1000
|
|
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u = getCryptFsUser()
|
|
u.QuotaFiles = 1000
|
|
cryptFsUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
|
|
// cleanup home dir
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
rule1.Conditions.Options.Names = []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: user.Username,
|
|
},
|
|
}
|
|
_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
expectedQuotaSize := int64(len(testFileContent))
|
|
expectedQuotaFiles := 1
|
|
if user.Username == cryptFsUser.Username {
|
|
encryptedFileSize, err := getEncryptedFileSize(expectedQuotaSize)
|
|
assert.NoError(t, err)
|
|
expectedQuotaSize = encryptedFileSize
|
|
}
|
|
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
info, err := client.Stat(testFileName + ".zip") //nolint:goconst
|
|
if assert.NoError(t, err) {
|
|
assert.Greater(t, info.Size(), int64(0))
|
|
// check quota
|
|
archiveSize := info.Size()
|
|
if user.Username == cryptFsUser.Username {
|
|
encryptedFileSize, err := getEncryptedFileSize(archiveSize)
|
|
assert.NoError(t, err)
|
|
archiveSize = encryptedFileSize
|
|
}
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaFiles+1, user.UsedQuotaFiles,
|
|
"quota file does no match for user %q", user.Username)
|
|
assert.Equal(t, expectedQuotaSize+archiveSize, user.UsedQuotaSize,
|
|
"quota size does no match for user %q", user.Username)
|
|
}
|
|
// now overwrite the same file
|
|
f, err = client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
info, err = client.Stat(testFileName + ".zip")
|
|
if assert.NoError(t, err) {
|
|
assert.Greater(t, info.Size(), int64(0))
|
|
archiveSize := info.Size()
|
|
if user.Username == cryptFsUser.Username {
|
|
encryptedFileSize, err := getEncryptedFileSize(archiveSize)
|
|
assert.NoError(t, err)
|
|
archiveSize = encryptedFileSize
|
|
}
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedQuotaFiles+1, user.UsedQuotaFiles,
|
|
"quota file after overwrite does no match for user %q", user.Username)
|
|
assert.Equal(t, expectedQuotaSize+archiveSize, user.UsedQuotaSize,
|
|
"quota size after overwrite does no match for user %q", user.Username)
|
|
}
|
|
}
|
|
if user.Username == localUser.Username {
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(localUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(cryptFsUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionCompressQuotaErrors(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
testDir := "archiveDir"
|
|
zipPath := "/archive.zip"
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionCompress,
|
|
Compress: dataprovider.EventActionFsCompress{
|
|
Name: zipPath,
|
|
Paths: []string{"/" + testDir},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "action2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"Compress failed"`,
|
|
Body: "Error: {{ErrorString}}",
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test compress",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"rename"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
fileSize := int64(100)
|
|
u := getTestUser()
|
|
u.QuotaSize = 10 * fileSize
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = client.MkdirAll(path.Join(testDir, "1", "1"))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, "1", testFileName), fileSize, client)
|
|
assert.NoError(t, err)
|
|
err = client.MkdirAll(path.Join(testDir, "2", "2"))
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, "2", testFileName), fileSize, client)
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(path.Join(testDir, "2", testFileName), path.Join(testDir, "2", testFileName+"_link"))
|
|
assert.NoError(t, err)
|
|
// trigger the compress action
|
|
err = client.Mkdir("a")
|
|
assert.NoError(t, err)
|
|
err = client.Rename("a", "b")
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
_, err := client.Stat(zipPath)
|
|
return err == nil
|
|
}, 3*time.Second, 100*time.Millisecond)
|
|
err = client.Remove(zipPath)
|
|
assert.NoError(t, err)
|
|
// add other 6 file, the compress action should fail with a quota error
|
|
err = writeSFTPFile(path.Join(testDir, "1", "1", testFileName), fileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, "2", "2", testFileName), fileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, "1", "1", testFileName+"1"), fileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, "2", "2", testFileName+"2"), fileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, "1", testFileName+"1"), fileSize, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, "2", testFileName+"2"), fileSize, client)
|
|
assert.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
err = client.Rename("b", "a")
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3*time.Second, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
|
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
|
// update quota size so the user is already overquota
|
|
user.QuotaSize = 7 * fileSize
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
err = client.Rename("a", "b")
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3*time.Second, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
|
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
|
// remove the path to compress to trigger an error for size estimation
|
|
out, err := runSSHCommand(fmt.Sprintf("sftpgo-remove %s", testDir), user)
|
|
assert.NoError(t, err, string(out))
|
|
lastReceivedEmail.reset()
|
|
err = client.Rename("b", "a")
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3*time.Second, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
|
assert.Contains(t, email.Data, "unable to estimate archive size")
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionCompressQuotaFolder(t *testing.T) {
|
|
testDir := "/folder"
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionCompress,
|
|
Compress: dataprovider.EventActionFsCompress{
|
|
Name: "/{{VirtualPath}}.zip",
|
|
Paths: []string{"/{{VirtualPath}}", testDir},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test compress",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getTestUser()
|
|
u.QuotaFiles = 1000
|
|
mappedPath := filepath.Join(os.TempDir(), "virtualpath")
|
|
folderName := filepath.Base(mappedPath)
|
|
vdirPath := "/virtualpath"
|
|
f := vfs.BaseVirtualFolder{
|
|
Name: folderName,
|
|
MappedPath: mappedPath,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName,
|
|
},
|
|
VirtualPath: vdirPath,
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
expectedQuotaSize := int64(len(testFileContent))
|
|
expectedQuotaFiles := 1
|
|
err = client.Symlink(path.Join(testDir, testFileName), path.Join(testDir, testFileName+"_link"))
|
|
assert.NoError(t, err)
|
|
f, err := client.Create(path.Join(testDir, testFileName))
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
info, err := client.Stat(path.Join(testDir, testFileName) + ".zip")
|
|
if assert.NoError(t, err) {
|
|
assert.Greater(t, info.Size(), int64(0))
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles++
|
|
expectedQuotaSize += info.Size()
|
|
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
|
}
|
|
vfolder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, vfolder.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), vfolder.UsedQuotaSize)
|
|
// upload in the virtual path
|
|
f, err = client.Create(path.Join(vdirPath, testFileName))
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
info, err = client.Stat(path.Join(vdirPath, testFileName) + ".zip")
|
|
if assert.NoError(t, err) {
|
|
assert.Greater(t, info.Size(), int64(0))
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
expectedQuotaFiles += 2
|
|
expectedQuotaSize += info.Size() + int64(len(testFileContent))
|
|
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
|
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
|
vfolder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, vfolder.UsedQuotaFiles)
|
|
assert.Equal(t, int64(0), vfolder.UsedQuotaSize)
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionCompressErrors(t *testing.T) {
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionCompress,
|
|
Compress: dataprovider.EventActionFsCompress{
|
|
Name: "/{{VirtualPath}}.zip",
|
|
Paths: []string{"/{{VirtualPath}}.zip"}, // cannot compress itself
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test compress",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
}
|
|
// try to compress a missing file
|
|
action1.Options.FsConfig.Compress.Paths = []string{"/missing file"}
|
|
_, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
}
|
|
// try to overwrite a directory
|
|
testDir := "/adir"
|
|
action1.Options.FsConfig.Compress.Name = testDir
|
|
action1.Options.FsConfig.Compress.Paths = []string{"/{{VirtualPath}}"}
|
|
_, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionEmailAttachments(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeFilesystem,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
FsConfig: dataprovider.EventActionFilesystemConfig{
|
|
Type: dataprovider.FilesystemActionCompress,
|
|
Compress: dataprovider.EventActionFsCompress{
|
|
Name: "/archive/{{VirtualPath}}.zip",
|
|
Paths: []string{"/{{VirtualPath}}"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "action2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"{{Event}}" from "{{Name}}"`,
|
|
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}} {{EscapedVirtualPath}}",
|
|
Attachments: []string{"/archive/{{VirtualPath}}.zip"},
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test email with attachment",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getTestSFTPUser()
|
|
u.FsConfig.SFTPConfig.BufferSize = 1
|
|
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
cryptFsUser, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: "upload" from`)
|
|
assert.Contains(t, email.Data, url.QueryEscape("/"+testFileName))
|
|
assert.Contains(t, email.Data, "Content-Disposition: attachment")
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(localUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(cryptFsUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventActionsRetentionReports(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
testDir := "/d"
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeDataRetentionCheck,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
RetentionConfig: dataprovider.EventActionDataRetentionConfig{
|
|
Folders: []dataprovider.FolderRetention{
|
|
{
|
|
Path: testDir,
|
|
Retention: 1,
|
|
DeleteEmptyDirs: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "action2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"{{Event}}" from "{{Name}}"`,
|
|
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}}",
|
|
Attachments: []string{dataprovider.RetentionReportPlaceHolder},
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a3 := dataprovider.BaseEventAction{
|
|
Name: "action3",
|
|
Type: dataprovider.ActionTypeHTTP,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
HTTPConfig: dataprovider.EventActionHTTPConfig{
|
|
Endpoint: fmt.Sprintf("http://%s/", httpAddr),
|
|
Timeout: 20,
|
|
Method: http.MethodPost,
|
|
Body: dataprovider.RetentionReportPlaceHolder,
|
|
},
|
|
},
|
|
}
|
|
action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a4 := dataprovider.BaseEventAction{
|
|
Name: "action4",
|
|
Type: dataprovider.ActionTypeHTTP,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
HTTPConfig: dataprovider.EventActionHTTPConfig{
|
|
Endpoint: fmt.Sprintf("http://%s/multipart", httpAddr),
|
|
Timeout: 20,
|
|
Method: http.MethodPost,
|
|
Parts: []dataprovider.HTTPPart{
|
|
{
|
|
Name: "reports.zip",
|
|
Filepath: dataprovider.RetentionReportPlaceHolder,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
action4, _, err := httpdtest.AddEventAction(a4, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: true,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: true,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 3,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: true,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action4.Name,
|
|
},
|
|
Order: 4,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
subdir := path.Join(testDir, "sub")
|
|
err = client.MkdirAll(subdir)
|
|
assert.NoError(t, err)
|
|
|
|
lastReceivedEmail.reset()
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "upload" from "%s"`, user.Username))
|
|
assert.Contains(t, email.Data, "Content-Disposition: attachment")
|
|
_, err = client.Stat(testDir)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(subdir)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
|
|
err = client.Mkdir(subdir)
|
|
assert.NoError(t, err)
|
|
newName := path.Join(testDir, testFileName)
|
|
err = client.Rename(testFileName, newName)
|
|
assert.NoError(t, err)
|
|
err = client.Chtimes(newName, time.Now().Add(-24*time.Hour), time.Now().Add(-24*time.Hour))
|
|
assert.NoError(t, err)
|
|
|
|
lastReceivedEmail.reset()
|
|
f, err = client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
_, err = client.Stat(subdir)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
_, err = client.Stat(subdir)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
// now remove the retention check to test errors
|
|
rule1.Actions = []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: false,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 3,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: false,
|
|
},
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action4.Name,
|
|
},
|
|
Order: 4,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
StopOnFailure: false,
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
f, err := client.Create(testFileName)
|
|
assert.NoError(t, err)
|
|
_, err = f.Write(testFileContent)
|
|
assert.NoError(t, err)
|
|
err = f.Close()
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"{{Event}}" from "{{Name}}"`,
|
|
Body: "Fs path {{FsPath}}, size: {{FileSize}}, protocol: {{Protocol}}, IP: {{IP}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test first upload rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"first-upload"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r2 := dataprovider.EventRule{
|
|
Name: "test first download rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"first-download"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
testFileSize := int64(32768)
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-upload" from "%s"`, user.Username))
|
|
lastReceivedEmail.reset()
|
|
// a new upload will not produce a new notification
|
|
err = writeSFTPFileNoCheck(testFileName+"_1", 32768, client)
|
|
assert.NoError(t, err)
|
|
assert.Never(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1000*time.Millisecond, 100*time.Millisecond)
|
|
// the same for download
|
|
f, err := client.Open(testFileName)
|
|
assert.NoError(t, err)
|
|
contents := make([]byte, testFileSize)
|
|
n, err := io.ReadFull(f, contents)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int(testFileSize), n)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-download" from "%s"`, user.Username))
|
|
// download again
|
|
lastReceivedEmail.reset()
|
|
f, err = client.Open(testFileName)
|
|
assert.NoError(t, err)
|
|
contents = make([]byte, testFileSize)
|
|
n, err = io.ReadFull(f, contents)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int(testFileSize), n)
|
|
err = f.Close()
|
|
assert.NoError(t, err)
|
|
assert.Never(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1000*time.Millisecond, 100*time.Millisecond)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleRenameEvent(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"{{Event}}" from "{{Name}}"`,
|
|
ContentType: 1,
|
|
Body: `<p>Fs path {{FsPath}}, Target path "{{VirtualTargetDirPath}}/{{TargetName}}", size: {{FileSize}}</p>`,
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rename rule",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"rename"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
testFileSize := int64(32768)
|
|
lastReceivedEmail.reset()
|
|
err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir("subdir")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName, path.Join("/subdir", testFileName))
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "rename" from "%s"`, user.Username))
|
|
assert.Contains(t, email.Data, "Content-Type: text/html")
|
|
assert.Contains(t, email.Data, fmt.Sprintf("Target path %q", path.Join("/subdir", testFileName)))
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleIDPLogin(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
|
|
username := `test_"idp_"login`
|
|
custom1 := `cust"oa"1`
|
|
u := map[string]any{
|
|
"username": "{{Name}}",
|
|
"status": 1,
|
|
"home_dir": filepath.Join(os.TempDir(), "{{IDPFieldcustom1}}"),
|
|
"permissions": map[string][]string{
|
|
"/": {dataprovider.PermAny},
|
|
},
|
|
}
|
|
userTmpl, err := json.Marshal(u)
|
|
require.NoError(t, err)
|
|
a := map[string]any{
|
|
"username": "{{Name}}",
|
|
"status": 1,
|
|
"permissions": []string{dataprovider.PermAdminAny},
|
|
}
|
|
adminTmpl, err := json.Marshal(a)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeIDPAccountCheck,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
IDPConfig: dataprovider.EventActionIDPAccountCheck{
|
|
Mode: 1, // create if not exists
|
|
TemplateUser: string(userTmpl),
|
|
TemplateAdmin: string(adminTmpl),
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"{{Event}} {{StatusString}}"`,
|
|
Body: "{{Name}} Custom field: {{IDPFieldcustom1}}",
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule IDP login",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerIDPLogin,
|
|
Conditions: dataprovider.EventConditions{
|
|
IDPLoginEvent: dataprovider.IDPLoginUser,
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name, // the rule is not sync and will be skipped
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
customFields := map[string]any{
|
|
"custom1": custom1,
|
|
}
|
|
user, admin, err := common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginUser,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.Nil(t, user)
|
|
assert.Nil(t, admin)
|
|
assert.NoError(t, err)
|
|
|
|
rule1.Actions[0].Options.ExecuteSync = true
|
|
rule1, resp, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err, string(resp))
|
|
user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginUser,
|
|
Status: 1,
|
|
}, &customFields)
|
|
if assert.NotNil(t, user) {
|
|
assert.Equal(t, filepath.Join(os.TempDir(), custom1), user.GetHomeDir())
|
|
_, err = httpdtest.RemoveUser(*user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Nil(t, admin)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginUser))
|
|
assert.Contains(t, email.Data, username)
|
|
assert.Contains(t, email.Data, custom1)
|
|
|
|
user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginAdmin,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.Nil(t, user)
|
|
assert.Nil(t, admin)
|
|
assert.NoError(t, err)
|
|
|
|
rule1.Conditions.IDPLoginEvent = dataprovider.IDPLoginAny
|
|
rule1.Actions = []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
Order: 1,
|
|
},
|
|
}
|
|
rule1, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
r2 := dataprovider.EventRule{
|
|
Name: "test email on IDP login",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerIDPLogin,
|
|
Conditions: dataprovider.EventConditions{
|
|
IDPLoginEvent: dataprovider.IDPLoginAdmin,
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule2, resp, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
lastReceivedEmail.reset()
|
|
user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginAdmin,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.Nil(t, user)
|
|
if assert.NotNil(t, admin) {
|
|
assert.Equal(t, 1, admin.Status)
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginAdmin))
|
|
assert.Contains(t, email.Data, username)
|
|
assert.Contains(t, email.Data, custom1)
|
|
admin.Status = 0
|
|
_, _, err = httpdtest.UpdateAdmin(*admin, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginAdmin,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.Nil(t, user)
|
|
if assert.NotNil(t, admin) {
|
|
assert.Equal(t, 0, admin.Status)
|
|
}
|
|
assert.NoError(t, err)
|
|
action1.Options.IDPConfig.Mode = 0
|
|
action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginAdmin,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.Nil(t, user)
|
|
if assert.NotNil(t, admin) {
|
|
assert.Equal(t, 1, admin.Status)
|
|
}
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveAdmin(*admin, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
r3 := dataprovider.EventRule{
|
|
Name: "test rule2 IDP login",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerIDPLogin,
|
|
Conditions: dataprovider.EventConditions{
|
|
IDPLoginEvent: dataprovider.IDPLoginAny,
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
Options: dataprovider.EventActionOptions{
|
|
ExecuteSync: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule3, resp, err := httpdtest.AddEventRule(r3, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginAdmin,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.Nil(t, user)
|
|
assert.Nil(t, admin)
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "more than one account check action rules matches")
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
action1.Options.IDPConfig.TemplateAdmin = `{}`
|
|
action1, _, err = httpdtest.UpdateEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, _, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginAdmin,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.ErrorIs(t, err, util.ErrValidation)
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
user, admin, err = common.HandleIDPLoginEvent(common.EventParams{
|
|
Name: username,
|
|
Event: common.IDPLoginAdmin,
|
|
Status: 1,
|
|
}, &customFields)
|
|
assert.Nil(t, user)
|
|
assert.Nil(t, admin)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleEmailField(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"{{Email}}"},
|
|
Subject: `"{{Event}}" from "{{Name}}"`,
|
|
Body: "Sample email body",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "action2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"failure@example.com"},
|
|
Subject: `"Failure`,
|
|
Body: "{{ErrorString}}",
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "r1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"mkdir"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
r2 := dataprovider.EventRule{
|
|
Name: "test rule2",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerProviderEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
ProviderEvents: []string{"add"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u := getTestUser()
|
|
u.Email = "user@example.com"
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, user.Email))
|
|
assert.Contains(t, email.Data, `Subject: "add" from "admin"`)
|
|
|
|
// if we add a user without email the notification will fail
|
|
lastReceivedEmail.reset()
|
|
u1 := getTestUser()
|
|
u1.Username += "_1"
|
|
user1, _, err := httpdtest.AddUser(u1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
|
assert.Contains(t, email.Data, `no recipient addresses set`)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err = client.Mkdir(testFileName)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, user.Email))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "mkdir" from "%s"`, user.Username))
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleCertificate(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notify@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test@example.com"},
|
|
Subject: `"{{Event}} {{StatusString}}"`,
|
|
ContentType: 0,
|
|
Body: "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}} Date time: {{DateTime}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "action2",
|
|
Type: dataprovider.ActionTypeFolderQuotaReset,
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule certificate",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerCertificate,
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r2 := dataprovider.EventRule{
|
|
Name: "test rule 2",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerCertificate,
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
renewalEvent := "Certificate renewal"
|
|
|
|
common.HandleCertificateEvent(common.EventParams{
|
|
Name: "example.com",
|
|
Timestamp: time.Now(),
|
|
Status: 1,
|
|
Event: renewalEvent,
|
|
})
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
|
|
assert.Contains(t, email.Data, "Content-Type: text/plain")
|
|
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
|
|
|
lastReceivedEmail.reset()
|
|
dateTime := time.Now()
|
|
params := common.EventParams{
|
|
Name: "example.com",
|
|
Timestamp: dateTime,
|
|
Status: 2,
|
|
Event: renewalEvent,
|
|
}
|
|
errRenew := errors.New("generic renew error")
|
|
params.AddError(errRenew)
|
|
common.HandleCertificateEvent(params)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email = lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
|
|
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
|
assert.Contains(t, email.Data, dateTime.UTC().Format("2006-01-02T15:04:05.000"))
|
|
assert.Contains(t, email.Data, errRenew.Error())
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
// ignored no more certificate rules
|
|
common.HandleCertificateEvent(common.EventParams{
|
|
Name: "example.com",
|
|
Timestamp: time.Now(),
|
|
Status: 1,
|
|
Event: renewalEvent,
|
|
})
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleIPBlocked(t *testing.T) {
|
|
oldConfig := config.GetCommonConfig()
|
|
|
|
cfg := config.GetCommonConfig()
|
|
cfg.DefenderConfig.Enabled = true
|
|
cfg.DefenderConfig.Threshold = 3
|
|
cfg.DefenderConfig.ScoreLimitExceeded = 2
|
|
|
|
err := common.Initialize(cfg, 0)
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "action1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"test3@example.com", "test4@example.com"},
|
|
Subject: `New "{{Event}}"`,
|
|
Body: "IP: {{IP}} Timestamp: {{Timestamp}}",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "action2",
|
|
Type: dataprovider.ActionTypeFolderQuotaReset,
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "test rule ip blocked",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerIPBlocked,
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r2 := dataprovider.EventRule{
|
|
Name: "test rule 2",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerIPBlocked,
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
lastReceivedEmail.reset()
|
|
time.Sleep(300 * time.Millisecond)
|
|
assert.Empty(t, lastReceivedEmail.get().From, lastReceivedEmail.get().Data)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
user.Password = "wrong_pwd"
|
|
_, _, err = getSftpClient(user)
|
|
assert.Error(t, err)
|
|
}
|
|
// the client is now banned
|
|
user.Password = defaultPassword
|
|
_, _, err = getSftpClient(user)
|
|
assert.Error(t, err)
|
|
// check the email notification
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 2)
|
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
|
assert.True(t, slices.Contains(email.To, "test4@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: New "IP Blocked"`)
|
|
|
|
err = dataprovider.DeleteEventRule(rule1.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteEventRule(rule2.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteEventAction(action1.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteEventAction(action2.Name, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = dataprovider.DeleteUser(user.Username, "", "", "")
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
err = common.Initialize(oldConfig, 0)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleRotateLog(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeRotateLogs,
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"success@example.net"},
|
|
Subject: `OK`,
|
|
Body: "OK action",
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"mkdir"},
|
|
Options: dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: user.Username,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err := client.Mkdir("just a test dir")
|
|
assert.NoError(t, err)
|
|
// just check that the action is executed
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.Contains(t, email.To, "success@example.net")
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRuleInactivityCheck(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeUserInactivityCheck,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
UserInactivityConfig: dataprovider.EventActionUserInactivity{
|
|
DisableThreshold: 10,
|
|
DeleteThreshold: 20,
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"success@example.net"},
|
|
Subject: `OK`,
|
|
Body: "OK action",
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"mkdir"},
|
|
Options: dataprovider.ConditionOptions{
|
|
Names: []dataprovider.ConditionPattern{
|
|
{
|
|
Pattern: user.Username,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
},
|
|
}
|
|
rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err := client.Mkdir("just a test dir")
|
|
assert.NoError(t, err)
|
|
// just check that the action is executed
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.Contains(t, email.To, "success@example.net")
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEventRulePasswordExpiration(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"failure@example.net"},
|
|
Subject: `Failure`,
|
|
Body: "Failure action",
|
|
},
|
|
},
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypePasswordExpirationCheck,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
PwdExpirationConfig: dataprovider.EventActionPasswordExpiration{
|
|
Threshold: 10,
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a3 := dataprovider.BaseEventAction{
|
|
Name: "a3",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"success@example.net"},
|
|
Subject: `OK`,
|
|
Body: "OK action",
|
|
},
|
|
},
|
|
}
|
|
action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerFsEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
FsEvents: []string{"mkdir"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action3.Name,
|
|
},
|
|
Order: 2,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, resp, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
dirName := "aTestDir"
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err := client.Mkdir(dirName)
|
|
assert.NoError(t, err)
|
|
// the user has no password expiration, the check will be skipped and the ok action executed
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.Contains(t, email.To, "success@example.net")
|
|
err = client.RemoveDirectory(dirName)
|
|
assert.NoError(t, err)
|
|
}
|
|
user.Filters.PasswordExpiration = 20
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err := client.Mkdir(dirName)
|
|
assert.NoError(t, err)
|
|
// the passowrd is not about to expire, the check will be skipped and the ok action executed
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.Contains(t, email.To, "success@example.net")
|
|
err = client.RemoveDirectory(dirName)
|
|
assert.NoError(t, err)
|
|
}
|
|
user.Filters.PasswordExpiration = 5
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err := client.Mkdir(dirName)
|
|
assert.NoError(t, err)
|
|
// the passowrd is about to expire, the user has no email, the failure action will be executed
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.Contains(t, email.To, "failure@example.net")
|
|
err = client.RemoveDirectory(dirName)
|
|
assert.NoError(t, err)
|
|
}
|
|
// remove the success action
|
|
rule1.Actions = []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
user.Email = "user@example.net"
|
|
user.Filters.AdditionalEmails = []string{"additional@example.net"}
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
lastReceivedEmail.reset()
|
|
err := client.Mkdir(dirName)
|
|
assert.NoError(t, err)
|
|
// the passowrd expiration will be notified
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 2)
|
|
assert.Contains(t, email.To, user.Email)
|
|
assert.Contains(t, email.To, user.Filters.AdditionalEmails[0])
|
|
assert.Contains(t, email.Data, "your SFTPGo password expires in 5 days")
|
|
err = client.RemoveDirectory(dirName)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSyncUploadAction(t *testing.T) {
|
|
if runtime.GOOS == osWindows {
|
|
t.Skip("this test is not available on Windows")
|
|
}
|
|
uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
|
|
common.Config.Actions.ExecuteOn = []string{"upload"}
|
|
common.Config.Actions.ExecuteSync = []string{"upload"}
|
|
common.Config.Actions.Hook = uploadScriptPath
|
|
|
|
u := getTestUser()
|
|
u.QuotaFiles = 1000
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
movedFileName := "moved.dat"
|
|
movedPath := filepath.Join(user.HomeDir, movedFileName)
|
|
err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 0), 0755)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
size := int64(32768)
|
|
err = writeSFTPFileNoCheck(testFileName, size, client)
|
|
assert.NoError(t, err)
|
|
_, err = client.Stat(testFileName)
|
|
assert.Error(t, err)
|
|
info, err := client.Stat(movedFileName)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, size, info.Size())
|
|
}
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, size, user.UsedQuotaSize)
|
|
// test some hook failure
|
|
// the uploaded file is moved and the hook fails, it will be not removed from the quota
|
|
err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 1), 0755)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFileNoCheck(testFileName+"_1", size, client)
|
|
assert.Error(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, size*2, user.UsedQuotaSize)
|
|
|
|
// the uploaded file is not moved and the hook fails, the uploaded file will be deleted
|
|
// and removed from the quota
|
|
movedPath = filepath.Join(user.HomeDir, "missing dir", movedFileName)
|
|
err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath, "", 1), 0755)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFileNoCheck(testFileName+"_2", size, client)
|
|
assert.Error(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, user.UsedQuotaFiles)
|
|
assert.Equal(t, size*2, user.UsedQuotaSize)
|
|
// overwrite an existing file
|
|
_, err = client.Stat(movedFileName)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFileNoCheck(movedFileName, size, client)
|
|
assert.Error(t, err)
|
|
_, err = client.Stat(movedFileName)
|
|
assert.Error(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
assert.Equal(t, size, user.UsedQuotaSize)
|
|
}
|
|
|
|
err = os.Remove(uploadScriptPath)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
common.Config.Actions.ExecuteOn = nil
|
|
common.Config.Actions.ExecuteSync = nil
|
|
common.Config.Actions.Hook = uploadScriptPath
|
|
}
|
|
|
|
func TestQuotaTrackDisabled(t *testing.T) {
|
|
err := dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf := config.GetProviderConf()
|
|
providerConf.TrackQuota = 0
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
|
|
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = writeSFTPFile(testFileName, 32, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName, testFileName+"1")
|
|
assert.NoError(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf = config.GetProviderConf()
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestGetQuotaError(t *testing.T) {
|
|
if dataprovider.GetProviderStatus().Driver == "memory" {
|
|
t.Skip("this test is not available with the memory provider")
|
|
}
|
|
u := getTestUser()
|
|
u.TotalDataTransfer = 2000
|
|
mappedPath := filepath.Join(os.TempDir(), "vdir")
|
|
folderName := filepath.Base(mappedPath)
|
|
vdirPath := "/vpath"
|
|
f := vfs.BaseVirtualFolder{
|
|
Name: folderName,
|
|
MappedPath: mappedPath,
|
|
}
|
|
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName,
|
|
},
|
|
VirtualPath: vdirPath,
|
|
QuotaSize: 0,
|
|
QuotaFiles: 10,
|
|
})
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = writeSFTPFile(testFileName, 32, client)
|
|
assert.NoError(t, err)
|
|
|
|
err = dataprovider.Close()
|
|
assert.NoError(t, err)
|
|
|
|
err = client.Rename(testFileName, path.Join(vdirPath, testFileName))
|
|
assert.Error(t, err)
|
|
|
|
err = config.LoadConfig(configDir, "")
|
|
assert.NoError(t, err)
|
|
providerConf := config.GetProviderConf()
|
|
err = dataprovider.Initialize(providerConf, configDir, true)
|
|
assert.NoError(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPath)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRetentionAPI(t *testing.T) {
|
|
u := getTestUser()
|
|
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
|
|
dataprovider.PermOverwrite, dataprovider.PermDownload, dataprovider.PermCreateDirs,
|
|
dataprovider.PermChtimes}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
uploadPath := path.Join(testDir, testFileName)
|
|
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(uploadPath, 32, client)
|
|
assert.NoError(t, err)
|
|
|
|
folderRetention := []dataprovider.FolderRetention{
|
|
{
|
|
Path: "/",
|
|
Retention: 24,
|
|
DeleteEmptyDirs: true,
|
|
},
|
|
}
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
_, err = client.Stat(uploadPath)
|
|
assert.NoError(t, err)
|
|
|
|
err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
_, err = client.Stat(uploadPath)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
|
|
_, err = client.Stat(testDir)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(uploadPath, 32, client)
|
|
assert.NoError(t, err)
|
|
|
|
folderRetention[0].DeleteEmptyDirs = false
|
|
err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
_, err = client.Stat(uploadPath)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
|
|
_, err = client.Stat(testDir)
|
|
assert.NoError(t, err)
|
|
|
|
err = writeSFTPFile(uploadPath, 32, client)
|
|
assert.NoError(t, err)
|
|
err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// remove delete permissions to the user, it will be automatically granted
|
|
user.Permissions["/"+testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
|
|
dataprovider.PermCreateDirs, dataprovider.PermChtimes}
|
|
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
innerUploadFilePath := path.Join("/"+testDir, testDir, testFileName)
|
|
err = client.Mkdir(path.Join(testDir, testDir))
|
|
assert.NoError(t, err)
|
|
|
|
err = writeSFTPFile(innerUploadFilePath, 32, client)
|
|
assert.NoError(t, err)
|
|
err = client.Chtimes(innerUploadFilePath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
|
|
assert.NoError(t, err)
|
|
|
|
folderRetention := []dataprovider.FolderRetention{
|
|
{
|
|
Path: "/missing",
|
|
Retention: 24,
|
|
},
|
|
{
|
|
Path: "/" + testDir,
|
|
Retention: 24,
|
|
DeleteEmptyDirs: true,
|
|
},
|
|
{
|
|
Path: path.Dir(innerUploadFilePath),
|
|
Retention: 0,
|
|
},
|
|
}
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
_, err = client.Stat(uploadPath)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
_, err = client.Stat(innerUploadFilePath)
|
|
assert.NoError(t, err)
|
|
|
|
folderRetention = []dataprovider.FolderRetention{
|
|
|
|
{
|
|
Path: "/" + testDir,
|
|
Retention: 24,
|
|
DeleteEmptyDirs: true,
|
|
},
|
|
}
|
|
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
_, err = client.Stat(innerUploadFilePath)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
// finally test some errors removing files or folders
|
|
if runtime.GOOS != osWindows {
|
|
dirPath := filepath.Join(user.HomeDir, "adir", "sub")
|
|
err := os.MkdirAll(dirPath, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
filePath := filepath.Join(dirPath, "f.dat")
|
|
err = os.WriteFile(filePath, nil, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
|
|
err = os.Chtimes(filePath, time.Now().Add(-72*time.Hour), time.Now().Add(-72*time.Hour))
|
|
assert.NoError(t, err)
|
|
|
|
err = os.Chmod(dirPath, 0001)
|
|
assert.NoError(t, err)
|
|
|
|
folderRetention := []dataprovider.FolderRetention{
|
|
|
|
{
|
|
Path: "/adir",
|
|
Retention: 24,
|
|
DeleteEmptyDirs: true,
|
|
},
|
|
}
|
|
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
err = os.Chmod(dirPath, 0555)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
err = os.Chmod(dirPath, os.ModePerm)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Eventually(t, func() bool {
|
|
return len(common.RetentionChecks.Get("")) == 0
|
|
}, 1000*time.Millisecond, 50*time.Millisecond)
|
|
|
|
assert.NoDirExists(t, dirPath)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRenameDir(t *testing.T) {
|
|
u := getTestUser()
|
|
testDir := "/dir-to-rename"
|
|
u.Permissions[testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, testFileName), 32, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testDir, testDir+"_rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestBuiltinKeyboardInteractiveAuthentication(t *testing.T) {
|
|
u := getTestUser()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
authMethods := []ssh.AuthMethod{
|
|
ssh.KeyboardInteractive(func(_, _ string, _ []string, _ []bool) ([]string, error) {
|
|
return []string{defaultPassword}, nil
|
|
}),
|
|
}
|
|
conn, client, err := getCustomAuthSftpClient(user, authMethods)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
}
|
|
// add multi-factor authentication
|
|
configName, key, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
|
|
assert.NoError(t, err)
|
|
user.Password = defaultPassword
|
|
user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
|
|
Enabled: true,
|
|
ConfigName: configName,
|
|
Secret: kms.NewPlainSecret(key.Secret()),
|
|
Protocols: []string{common.ProtocolSSH},
|
|
}
|
|
err = dataprovider.UpdateUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
passcode, err := generateTOTPPasscode(key.Secret(), otp.AlgorithmSHA1)
|
|
assert.NoError(t, err)
|
|
passwordAsked := false
|
|
passcodeAsked := false
|
|
authMethods = []ssh.AuthMethod{
|
|
ssh.KeyboardInteractive(func(_, _ string, questions []string, _ []bool) ([]string, error) {
|
|
var answers []string
|
|
if strings.HasPrefix(questions[0], "Password") {
|
|
answers = append(answers, defaultPassword)
|
|
passwordAsked = true
|
|
} else {
|
|
answers = append(answers, passcode)
|
|
passcodeAsked = true
|
|
}
|
|
return answers, nil
|
|
}),
|
|
}
|
|
conn, client, err = getCustomAuthSftpClient(user, authMethods)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.True(t, passwordAsked)
|
|
assert.True(t, passcodeAsked)
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestMultiStepBuiltinKeyboardAuth(t *testing.T) {
|
|
u := getTestUser()
|
|
u.PublicKeys = []string{testPubKey}
|
|
u.Filters.DeniedLoginMethods = []string{
|
|
dataprovider.SSHLoginMethodPublicKey,
|
|
dataprovider.LoginMethodPassword,
|
|
dataprovider.SSHLoginMethodKeyboardInteractive,
|
|
}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
signer, err := ssh.ParsePrivateKey([]byte(testPrivateKey))
|
|
assert.NoError(t, err)
|
|
// public key + password
|
|
authMethods := []ssh.AuthMethod{
|
|
ssh.PublicKeys(signer),
|
|
ssh.KeyboardInteractive(func(_, _ string, _ []string, _ []bool) ([]string, error) {
|
|
return []string{defaultPassword}, nil
|
|
}),
|
|
}
|
|
conn, client, err := getCustomAuthSftpClient(user, authMethods)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
}
|
|
// add multi-factor authentication
|
|
configName, key, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
|
|
assert.NoError(t, err)
|
|
user.Password = defaultPassword
|
|
user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
|
|
Enabled: true,
|
|
ConfigName: configName,
|
|
Secret: kms.NewPlainSecret(key.Secret()),
|
|
Protocols: []string{common.ProtocolSSH},
|
|
}
|
|
err = dataprovider.UpdateUser(&user, "", "", "")
|
|
assert.NoError(t, err)
|
|
passcode, err := generateTOTPPasscode(key.Secret(), otp.AlgorithmSHA1)
|
|
assert.NoError(t, err)
|
|
// public key + passcode
|
|
authMethods = []ssh.AuthMethod{
|
|
ssh.PublicKeys(signer),
|
|
ssh.KeyboardInteractive(func(_, _ string, _ []string, _ []bool) ([]string, error) {
|
|
return []string{passcode}, nil
|
|
}),
|
|
}
|
|
conn, client, err = getCustomAuthSftpClient(user, authMethods)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestRenameSymlink(t *testing.T) {
|
|
u := getTestUser()
|
|
testDir := "/dir-no-create-links"
|
|
otherDir := "otherdir"
|
|
u.Permissions[testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete,
|
|
dataprovider.PermCreateDirs}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = client.Mkdir(otherDir)
|
|
assert.NoError(t, err)
|
|
err = client.Symlink(otherDir, otherDir+".link")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(otherDir+".link", path.Join(testDir, "symlink"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(otherDir+".link", "allowed_link")
|
|
assert.NoError(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestSplittedDeletePerms(t *testing.T) {
|
|
u := getTestUser()
|
|
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDeleteDirs,
|
|
dataprovider.PermCreateDirs}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(testFileName)
|
|
assert.Error(t, err)
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory(testDir)
|
|
assert.NoError(t, err)
|
|
}
|
|
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDeleteFiles,
|
|
dataprovider.PermCreateDirs, dataprovider.PermOverwrite}
|
|
_, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
err = client.Remove(testFileName)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.RemoveDirectory(testDir)
|
|
assert.Error(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestSplittedRenamePerms(t *testing.T) {
|
|
u := getTestUser()
|
|
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermRenameDirs,
|
|
dataprovider.PermCreateDirs}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName, testFileName+"_renamed")
|
|
assert.Error(t, err)
|
|
err = client.Rename(testDir, testDir+"_renamed")
|
|
assert.NoError(t, err)
|
|
}
|
|
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermRenameFiles,
|
|
dataprovider.PermCreateDirs, dataprovider.PermOverwrite}
|
|
_, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
|
|
conn, client, err = getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testFileName, testFileName+"_renamed")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(testDir, testDir+"_renamed")
|
|
assert.Error(t, err)
|
|
}
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestSFTPLoopError(t *testing.T) {
|
|
smtpCfg := smtp.Config{
|
|
Host: "127.0.0.1",
|
|
Port: 2525,
|
|
From: "notification@example.com",
|
|
TemplatesPath: "templates",
|
|
}
|
|
err := smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
user1 := getTestUser()
|
|
user2 := getTestUser()
|
|
user1.Username += "1"
|
|
user2.Username += "2"
|
|
// user1 is a local account with a virtual SFTP folder to user2
|
|
// user2 has user1 as SFTP fs
|
|
f := vfs.BaseVirtualFolder{
|
|
Name: "sftp",
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: sftpServerAddr,
|
|
Username: user2.Username,
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
folder, _, err := httpdtest.AddFolder(f, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
user1.VirtualFolders = append(user1.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folder.Name,
|
|
},
|
|
VirtualPath: "/vdir",
|
|
})
|
|
|
|
user2.FsConfig.Provider = sdk.SFTPFilesystemProvider
|
|
user2.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: sftpServerAddr,
|
|
Username: user1.Username,
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
}
|
|
|
|
user1, resp, err := httpdtest.AddUser(user1, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
user2, resp, err = httpdtest.AddUser(user2, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
a1 := dataprovider.BaseEventAction{
|
|
Name: "a1",
|
|
Type: dataprovider.ActionTypeUserQuotaReset,
|
|
}
|
|
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
a2 := dataprovider.BaseEventAction{
|
|
Name: "a2",
|
|
Type: dataprovider.ActionTypeEmail,
|
|
Options: dataprovider.BaseEventActionOptions{
|
|
EmailConfig: dataprovider.EventActionEmailConfig{
|
|
Recipients: []string{"failure@example.com"},
|
|
Subject: `Failed action"`,
|
|
Body: "Test body",
|
|
},
|
|
},
|
|
}
|
|
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
r1 := dataprovider.EventRule{
|
|
Name: "rule1",
|
|
Status: 1,
|
|
Trigger: dataprovider.EventTriggerProviderEvent,
|
|
Conditions: dataprovider.EventConditions{
|
|
ProviderEvents: []string{"update"},
|
|
},
|
|
Actions: []dataprovider.EventAction{
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action1.Name,
|
|
},
|
|
Order: 1,
|
|
},
|
|
{
|
|
BaseEventAction: dataprovider.BaseEventAction{
|
|
Name: action2.Name,
|
|
},
|
|
Order: 2,
|
|
Options: dataprovider.EventActionOptions{
|
|
IsFailureAction: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
lastReceivedEmail.reset()
|
|
_, _, err = httpdtest.UpdateUser(user2, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
assert.Eventually(t, func() bool {
|
|
return lastReceivedEmail.get().From != ""
|
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
|
email := lastReceivedEmail.get()
|
|
assert.Len(t, email.To, 1)
|
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
|
assert.Contains(t, email.Data, `Subject: Failed action`)
|
|
|
|
user1.VirtualFolders[0].FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
|
|
user2.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
|
|
|
|
conn := common.NewBaseConnection("", common.ProtocolWebDAV, "", "", user1)
|
|
_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
|
|
conn = common.NewBaseConnection("", common.ProtocolSFTP, "", "", user1)
|
|
_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
|
|
assert.Error(t, err)
|
|
conn = common.NewBaseConnection("", common.ProtocolFTP, "", "", user1)
|
|
_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
|
|
assert.Error(t, err)
|
|
|
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user1, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user1.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(user2, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user2.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
|
|
smtpCfg = smtp.Config{}
|
|
err = smtpCfg.Initialize(configDir, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestNonLocalCrossRename(t *testing.T) {
|
|
baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
u := getTestUser()
|
|
u.HomeDir += "_folders"
|
|
u.Username += "_folders"
|
|
mappedPathSFTP := filepath.Join(os.TempDir(), "sftp")
|
|
folderNameSFTP := filepath.Base(mappedPathSFTP)
|
|
vdirSFTPPath := "/vdir/sftp"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderNameSFTP,
|
|
},
|
|
VirtualPath: vdirSFTPPath,
|
|
})
|
|
mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
|
|
folderNameCrypt := filepath.Base(mappedPathCrypt)
|
|
vdirCryptPath := "/vdir/crypt"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderNameCrypt,
|
|
},
|
|
VirtualPath: vdirCryptPath,
|
|
})
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderNameSFTP,
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: sftpServerAddr,
|
|
Username: baseUser.Username,
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderNameCrypt,
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.CryptedFilesystemProvider,
|
|
CryptConfig: vfs.CryptFsConfig{
|
|
Passphrase: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
MappedPath: mappedPathCrypt,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirSFTPPath, testFileName), 8192, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirCryptPath, testFileName), 16384, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirSFTPPath, testFileName), path.Join(vdirCryptPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirSFTPPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(testFileName, path.Join(vdirCryptPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(testFileName, path.Join(vdirSFTPPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join(vdirSFTPPath, testFileName), testFileName+".rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join(vdirCryptPath, testFileName), testFileName+".rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
// rename on local fs or on the same folder must work
|
|
err = client.Rename(testFileName, testFileName+".rename")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirSFTPPath, testFileName), path.Join(vdirSFTPPath, testFileName+"_rename"))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirCryptPath, testFileName+"_rename"))
|
|
assert.NoError(t, err)
|
|
// renaming a virtual folder is not allowed
|
|
err = client.Rename(vdirSFTPPath, vdirSFTPPath+"_rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(vdirCryptPath, vdirCryptPath+"_rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(vdirCryptPath, path.Join(vdirCryptPath, "rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Mkdir(path.Join(vdirCryptPath, "subcryptdir"))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirCryptPath, "subcryptdir"), vdirCryptPath)
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
// renaming root folder is not allowed
|
|
err = client.Rename("/", "new_name")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
// renaming a path to a virtual folder is not allowed
|
|
err = client.Rename("/vdir", "new_vdir")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameSFTP}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(baseUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPathCrypt)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPathSFTP)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestNonLocalCrossRenameNonLocalBaseUser(t *testing.T) {
|
|
baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
u := getTestSFTPUser()
|
|
mappedPathLocal := filepath.Join(os.TempDir(), "local")
|
|
folderNameLocal := filepath.Base(mappedPathLocal)
|
|
vdirLocalPath := "/vdir/local"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderNameLocal,
|
|
},
|
|
VirtualPath: vdirLocalPath,
|
|
})
|
|
mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
|
|
folderNameCrypt := filepath.Base(mappedPathCrypt)
|
|
vdirCryptPath := "/vdir/crypt"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderNameCrypt,
|
|
},
|
|
VirtualPath: vdirCryptPath,
|
|
})
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderNameLocal,
|
|
MappedPath: mappedPathLocal,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderNameCrypt,
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.CryptedFilesystemProvider,
|
|
CryptConfig: vfs.CryptFsConfig{
|
|
Passphrase: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
MappedPath: mappedPathCrypt,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
assert.NoError(t, checkBasicSFTP(client))
|
|
err = writeSFTPFile(testFileName, 4096, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirLocalPath, testFileName), 8192, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vdirCryptPath, testFileName), 16384, client)
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirLocalPath, testFileName), path.Join(vdirCryptPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirLocalPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(testFileName, path.Join(vdirCryptPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(testFileName, path.Join(vdirLocalPath, testFileName+".rename"))
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join(vdirLocalPath, testFileName), testFileName+".rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(path.Join(vdirCryptPath, testFileName), testFileName+".rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
// rename on local fs or on the same folder must work
|
|
err = client.Rename(testFileName, testFileName+".rename")
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirLocalPath, testFileName), path.Join(vdirLocalPath, testFileName+"_rename"))
|
|
assert.NoError(t, err)
|
|
err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirCryptPath, testFileName+"_rename"))
|
|
assert.NoError(t, err)
|
|
// renaming a virtual folder is not allowed
|
|
err = client.Rename(vdirLocalPath, vdirLocalPath+"_rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
err = client.Rename(vdirCryptPath, vdirCryptPath+"_rename")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
// renaming root folder is not allowed
|
|
err = client.Rename("/", "new_name")
|
|
assert.ErrorIs(t, err, os.ErrPermission)
|
|
// renaming a path to a virtual folder is not allowed
|
|
err = client.Rename("/vdir", "new_vdir")
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
|
|
}
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameLocal}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(baseUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPathCrypt)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(mappedPathLocal)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCopyAndRemoveSSHCommands(t *testing.T) {
|
|
u := getTestUser()
|
|
u.QuotaFiles = 1000
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
fileSize := int64(32)
|
|
err = writeSFTPFile(testFileName, fileSize, client)
|
|
assert.NoError(t, err)
|
|
|
|
testFileNameCopy := testFileName + "_copy"
|
|
out, err := runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
|
|
assert.NoError(t, err, string(out))
|
|
// the resolved destination path match the source path
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, path.Dir(testFileName)), user)
|
|
assert.Error(t, err, string(out))
|
|
|
|
info, err := client.Stat(testFileNameCopy)
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, fileSize, info.Size())
|
|
}
|
|
|
|
testDir := "test dir"
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s '%s'`, testFileName, testDir), user)
|
|
assert.NoError(t, err, string(out))
|
|
info, err = client.Stat(path.Join(testDir, testFileName))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, fileSize, info.Size())
|
|
}
|
|
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 3*fileSize, user.UsedQuotaSize)
|
|
assert.Equal(t, 3, user.UsedQuotaFiles)
|
|
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %s", testFileNameCopy), user)
|
|
assert.NoError(t, err, string(out))
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove '%s'`, testDir), user)
|
|
assert.NoError(t, err, string(out))
|
|
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, fileSize, user.UsedQuotaSize)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
|
|
_, err = client.Stat(testFileNameCopy)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
// create a dir tree
|
|
dir1 := "dir1"
|
|
dir2 := "dir 2"
|
|
err = client.MkdirAll(path.Join(dir1, dir2))
|
|
assert.NoError(t, err)
|
|
toCreate := []string{
|
|
path.Join(dir1, testFileName),
|
|
path.Join(dir1, dir2, testFileName),
|
|
}
|
|
for _, p := range toCreate {
|
|
err = writeSFTPFile(p, fileSize, client)
|
|
assert.NoError(t, err)
|
|
}
|
|
// create a symlink, copying a symlink is not supported
|
|
err = client.Symlink(path.Join("/", dir1, testFileName), path.Join("/", dir1, testFileName+"_link"))
|
|
assert.NoError(t, err)
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1, testFileName+"_link"),
|
|
path.Join("/", testFileName+"_link")), user)
|
|
assert.Error(t, err, string(out))
|
|
// copying a dir inside itself should fail
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1),
|
|
path.Join("/", dir1, "sub")), user)
|
|
assert.Error(t, err, string(out))
|
|
// copy source and dest must differ
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1),
|
|
path.Join("/", dir1)), user)
|
|
assert.Error(t, err, string(out))
|
|
// copy a missing file/dir should fail
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", "missing_entry"),
|
|
path.Join("/", dir1)), user)
|
|
assert.Error(t, err, string(out))
|
|
// try to overwrite a file with a dir
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join("/", dir1), testFileName), user)
|
|
assert.Error(t, err, string(out))
|
|
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s "%s"`, dir1, dir2), user)
|
|
assert.NoError(t, err, string(out))
|
|
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5*fileSize, user.UsedQuotaSize)
|
|
assert.Equal(t, 5, user.UsedQuotaFiles)
|
|
|
|
// copy again, quota must remain unchanged
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s/ "%s"`, dir1, dir2), user)
|
|
assert.NoError(t, err, string(out))
|
|
_, err = client.Stat(dir2)
|
|
assert.NoError(t, err)
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 5*fileSize, user.UsedQuotaSize)
|
|
assert.Equal(t, 5, user.UsedQuotaFiles)
|
|
// now copy inside target
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s "%s"`, dir1, dir2), user)
|
|
assert.NoError(t, err, string(out))
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 7*fileSize, user.UsedQuotaSize)
|
|
assert.Equal(t, 7, user.UsedQuotaFiles)
|
|
|
|
for _, p := range []string{dir1, dir2} {
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove "%s"`, p), user)
|
|
assert.NoError(t, err, string(out))
|
|
_, err = client.Stat(p)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, fileSize, user.UsedQuotaSize)
|
|
assert.Equal(t, 1, user.UsedQuotaFiles)
|
|
// test quota errors
|
|
user.QuotaFiles = 1
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
// quota files exceeded
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
|
|
assert.Error(t, err, string(out))
|
|
user.QuotaFiles = 1000
|
|
user.QuotaSize = fileSize + 1
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
// quota size exceeded after the copy
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
|
|
assert.Error(t, err, string(out))
|
|
user.QuotaSize = fileSize - 1
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
// quota size exceeded
|
|
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileNameCopy), user)
|
|
assert.Error(t, err, string(out))
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCopyAndRemovePermissions(t *testing.T) {
|
|
u := getTestUser()
|
|
restrictedPath := "/dir/path"
|
|
patternFilterPath := "/patterns"
|
|
u.Filters.FilePatterns = []sdk.PatternsFilter{
|
|
{
|
|
Path: patternFilterPath,
|
|
DeniedPatterns: []string{"*.dat"},
|
|
},
|
|
}
|
|
u.Permissions[restrictedPath] = []string{}
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
err = client.MkdirAll(restrictedPath)
|
|
assert.NoError(t, err)
|
|
err = client.MkdirAll(patternFilterPath)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(testFileName, 100, client)
|
|
assert.NoError(t, err)
|
|
// getting file writer will fail
|
|
out, err := runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, restrictedPath), user)
|
|
assert.Error(t, err, string(out))
|
|
// file pattern not allowed
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, patternFilterPath), user)
|
|
assert.Error(t, err, string(out))
|
|
|
|
testDir := path.Join("/", path.Base(restrictedPath))
|
|
err = client.Mkdir(testDir)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(testDir, testFileName), 100, client)
|
|
assert.NoError(t, err)
|
|
// creating target dir will fail
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s/`, testDir, restrictedPath), user)
|
|
assert.Error(t, err, string(out))
|
|
// get dir contents will fail
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s /`, restrictedPath), user)
|
|
assert.Error(t, err, string(out))
|
|
// get dir contents will fail
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove %s`, restrictedPath), user)
|
|
assert.Error(t, err, string(out))
|
|
// give list dir permissions and retry, now delete will fail
|
|
user.Permissions[restrictedPath] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
|
|
user.Permissions[testDir] = []string{dataprovider.PermListItems}
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
// no copy permission
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, restrictedPath), user)
|
|
assert.Error(t, err, string(out))
|
|
user.Permissions[restrictedPath] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermCopy}
|
|
user.Permissions[testDir] = []string{dataprovider.PermListItems, dataprovider.PermCopy}
|
|
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
|
assert.NoError(t, err)
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, restrictedPath), user)
|
|
assert.NoError(t, err, string(out))
|
|
// overwrite will fail, no permission
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, testFileName, restrictedPath), user)
|
|
assert.Error(t, err, string(out))
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-remove %s`, restrictedPath), user)
|
|
assert.Error(t, err, string(out))
|
|
// try to copy a file from testDir, we have only list permissions so getFileReader will fail
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, path.Join(testDir, testFileName), testFileName+".copy"), user)
|
|
assert.Error(t, err, string(out))
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestCrossFoldersCopy(t *testing.T) {
|
|
baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
|
|
u := getTestUser()
|
|
u.Username += "_1"
|
|
u.HomeDir = filepath.Join(os.TempDir(), u.Username)
|
|
u.QuotaFiles = 1000
|
|
mappedPath1 := filepath.Join(os.TempDir(), "mapped1")
|
|
folderName1 := filepath.Base(mappedPath1)
|
|
vpath1 := "/vdirs/vdir1"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
},
|
|
VirtualPath: vpath1,
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
})
|
|
mappedPath2 := filepath.Join(os.TempDir(), "mapped1", "dir", "mapped2")
|
|
folderName2 := filepath.Base(mappedPath2)
|
|
vpath2 := "/vdirs/vdir2"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
},
|
|
VirtualPath: vpath2,
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
})
|
|
mappedPath3 := filepath.Join(os.TempDir(), "mapped3")
|
|
folderName3 := filepath.Base(mappedPath3)
|
|
vpath3 := "/vdirs/vdir3"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName3,
|
|
},
|
|
VirtualPath: vpath3,
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
})
|
|
mappedPath4 := filepath.Join(os.TempDir(), "mapped4")
|
|
folderName4 := filepath.Base(mappedPath4)
|
|
vpath4 := "/vdirs/vdir4"
|
|
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
|
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
|
Name: folderName4,
|
|
},
|
|
VirtualPath: vpath4,
|
|
QuotaSize: -1,
|
|
QuotaFiles: -1,
|
|
})
|
|
f1 := vfs.BaseVirtualFolder{
|
|
Name: folderName1,
|
|
MappedPath: mappedPath1,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f2 := vfs.BaseVirtualFolder{
|
|
Name: folderName2,
|
|
MappedPath: mappedPath2,
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f3 := vfs.BaseVirtualFolder{
|
|
Name: folderName3,
|
|
MappedPath: mappedPath3,
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.CryptedFilesystemProvider,
|
|
CryptConfig: vfs.CryptFsConfig{
|
|
Passphrase: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f3, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
f4 := vfs.BaseVirtualFolder{
|
|
Name: folderName4,
|
|
MappedPath: mappedPath4,
|
|
FsConfig: vfs.Filesystem{
|
|
Provider: sdk.SFTPFilesystemProvider,
|
|
SFTPConfig: vfs.SFTPFsConfig{
|
|
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
|
|
Endpoint: sftpServerAddr,
|
|
Username: baseUser.Username,
|
|
},
|
|
Password: kms.NewPlainSecret(defaultPassword),
|
|
},
|
|
},
|
|
}
|
|
_, _, err = httpdtest.AddFolder(f4, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err, string(resp))
|
|
conn, client, err := getSftpClient(user)
|
|
if assert.NoError(t, err) {
|
|
defer conn.Close()
|
|
defer client.Close()
|
|
|
|
baseFileSize := int64(100)
|
|
err = writeSFTPFile(path.Join(vpath1, testFileName), baseFileSize+1, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vpath2, testFileName), baseFileSize+2, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vpath3, testFileName), baseFileSize+3, client)
|
|
assert.NoError(t, err)
|
|
err = writeSFTPFile(path.Join(vpath4, testFileName), baseFileSize+4, client)
|
|
assert.NoError(t, err)
|
|
// cannot remove a directory with virtual folders inside
|
|
out, err := runSSHCommand(fmt.Sprintf(`sftpgo-remove %s`, path.Dir(vpath1)), user)
|
|
assert.Error(t, err, string(out))
|
|
// copy across virtual folders
|
|
copyDir := "/copy"
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s/`, path.Dir(vpath1), copyDir), user)
|
|
assert.NoError(t, err, string(out))
|
|
// check the copy
|
|
info, err := client.Stat(path.Join(copyDir, vpath1, testFileName))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, baseFileSize+1, info.Size())
|
|
}
|
|
info, err = client.Stat(path.Join(copyDir, vpath2, testFileName))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, baseFileSize+2, info.Size())
|
|
}
|
|
info, err = client.Stat(path.Join(copyDir, vpath3, testFileName))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, baseFileSize+3, info.Size())
|
|
}
|
|
info, err = client.Stat(path.Join(copyDir, vpath4, testFileName))
|
|
if assert.NoError(t, err) {
|
|
assert.Equal(t, baseFileSize+4, info.Size())
|
|
}
|
|
// nested fs paths
|
|
out, err = runSSHCommand(fmt.Sprintf(`sftpgo-copy %s %s`, vpath1, vpath2), user)
|
|
assert.Error(t, err, string(out))
|
|
}
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(baseUser.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
for _, folderName := range []string{folderName1, folderName2, folderName3, folderName4} {
|
|
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(filepath.Join(os.TempDir(), folderName))
|
|
assert.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func TestHTTPFs(t *testing.T) {
|
|
u := getTestUserWithHTTPFs()
|
|
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
|
assert.NoError(t, err)
|
|
|
|
err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
|
|
assert.NoError(t, err)
|
|
|
|
conn := common.NewBaseConnection(xid.New().String(), common.ProtocolFTP, "", "", user)
|
|
err = conn.CreateDir(httpFsWellKnowDir, false)
|
|
assert.NoError(t, err)
|
|
|
|
err = os.WriteFile(filepath.Join(os.TempDir(), "httpfs", defaultHTTPFsUsername, httpFsWellKnowDir, "file.txt"), []byte("data"), 0666)
|
|
assert.NoError(t, err)
|
|
|
|
err = conn.Copy(httpFsWellKnowDir, httpFsWellKnowDir+"_copy")
|
|
assert.NoError(t, err)
|
|
|
|
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
|
assert.NoError(t, err)
|
|
err = os.RemoveAll(user.GetHomeDir())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestProxyProtocol(t *testing.T) {
|
|
resp, err := httpclient.Get(fmt.Sprintf("http://%v", httpProxyAddr))
|
|
if !assert.Error(t, err) {
|
|
resp.Body.Close()
|
|
}
|
|
}
|
|
|
|
func TestSetProtocol(t *testing.T) {
|
|
conn := common.NewBaseConnection("id", "sshd_exec", "", "", dataprovider.User{BaseUser: sdk.BaseUser{HomeDir: os.TempDir()}})
|
|
conn.SetProtocol(common.ProtocolSCP)
|
|
require.Equal(t, "SCP_id", conn.GetID())
|
|
}
|
|
|
|
func TestGetFsError(t *testing.T) {
|
|
u := getTestUser()
|
|
u.FsConfig.Provider = sdk.GCSFilesystemProvider
|
|
u.FsConfig.GCSConfig.Bucket = "test"
|
|
u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
|
|
conn := common.NewBaseConnection("", common.ProtocolFTP, "", "", u)
|
|
_, _, err := conn.GetFsAndResolvedPath("/vpath")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func waitTCPListening(address string) {
|
|
for {
|
|
conn, err := net.Dial("tcp", address)
|
|
if err != nil {
|
|
logger.WarnToConsole("tcp server %v not listening: %v", address, err)
|
|
time.Sleep(100 * time.Millisecond)
|
|
continue
|
|
}
|
|
logger.InfoToConsole("tcp server %v now listening", address)
|
|
conn.Close()
|
|
break
|
|
}
|
|
}
|
|
|
|
func checkBasicSFTP(client *sftp.Client) error {
|
|
_, err := client.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = client.ReadDir(".")
|
|
return err
|
|
}
|
|
|
|
func getCustomAuthSftpClient(user dataprovider.User, authMethods []ssh.AuthMethod) (*ssh.Client, *sftp.Client, error) {
|
|
var sftpClient *sftp.Client
|
|
config := &ssh.ClientConfig{
|
|
User: user.Username,
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Auth: authMethods,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
conn, err := ssh.Dial("tcp", sftpServerAddr, config)
|
|
if err != nil {
|
|
return conn, sftpClient, err
|
|
}
|
|
sftpClient, err = sftp.NewClient(conn)
|
|
if err != nil {
|
|
conn.Close()
|
|
}
|
|
return conn, sftpClient, err
|
|
}
|
|
|
|
func getSftpClient(user dataprovider.User) (*ssh.Client, *sftp.Client, error) {
|
|
var sftpClient *sftp.Client
|
|
config := &ssh.ClientConfig{
|
|
User: user.Username,
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
if user.Password != "" {
|
|
config.Auth = []ssh.AuthMethod{ssh.Password(user.Password)}
|
|
} else {
|
|
config.Auth = []ssh.AuthMethod{ssh.Password(defaultPassword)}
|
|
}
|
|
|
|
conn, err := ssh.Dial("tcp", sftpServerAddr, config)
|
|
if err != nil {
|
|
return conn, sftpClient, err
|
|
}
|
|
sftpClient, err = sftp.NewClient(conn)
|
|
if err != nil {
|
|
conn.Close()
|
|
}
|
|
return conn, sftpClient, err
|
|
}
|
|
|
|
func runSSHCommand(command string, user dataprovider.User) ([]byte, error) {
|
|
var sshSession *ssh.Session
|
|
var output []byte
|
|
config := &ssh.ClientConfig{
|
|
User: user.Username,
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
if user.Password != "" {
|
|
config.Auth = []ssh.AuthMethod{ssh.Password(user.Password)}
|
|
} else {
|
|
config.Auth = []ssh.AuthMethod{ssh.Password(defaultPassword)}
|
|
}
|
|
|
|
conn, err := ssh.Dial("tcp", sftpServerAddr, config)
|
|
if err != nil {
|
|
return output, err
|
|
}
|
|
defer conn.Close()
|
|
sshSession, err = conn.NewSession()
|
|
if err != nil {
|
|
return output, err
|
|
}
|
|
var stdout, stderr bytes.Buffer
|
|
sshSession.Stdout = &stdout
|
|
sshSession.Stderr = &stderr
|
|
err = sshSession.Run(command)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to run command %v: %v", command, stderr.Bytes())
|
|
}
|
|
return stdout.Bytes(), err
|
|
}
|
|
|
|
func getWebDavClient(user dataprovider.User) *gowebdav.Client {
|
|
rootPath := fmt.Sprintf("http://localhost:%d/", webDavServerPort)
|
|
pwd := defaultPassword
|
|
if user.Password != "" {
|
|
pwd = user.Password
|
|
}
|
|
client := gowebdav.NewClient(rootPath, user.Username, pwd)
|
|
client.SetTimeout(10 * time.Second)
|
|
return client
|
|
}
|
|
|
|
func getTestUser() dataprovider.User {
|
|
user := dataprovider.User{
|
|
BaseUser: sdk.BaseUser{
|
|
Username: defaultUsername,
|
|
Password: defaultPassword,
|
|
HomeDir: filepath.Join(homeBasePath, defaultUsername),
|
|
Status: 1,
|
|
ExpirationDate: 0,
|
|
},
|
|
}
|
|
user.Permissions = make(map[string][]string)
|
|
user.Permissions["/"] = allPerms
|
|
return user
|
|
}
|
|
|
|
func getTestSFTPUser() dataprovider.User {
|
|
u := getTestUser()
|
|
u.Username = defaultSFTPUsername
|
|
u.FsConfig.Provider = sdk.SFTPFilesystemProvider
|
|
u.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
|
|
u.FsConfig.SFTPConfig.Username = defaultUsername
|
|
u.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
|
|
return u
|
|
}
|
|
|
|
func getCryptFsUser() dataprovider.User {
|
|
u := getTestUser()
|
|
u.Username += "_crypt"
|
|
u.FsConfig.Provider = sdk.CryptedFilesystemProvider
|
|
u.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret(defaultPassword)
|
|
return u
|
|
}
|
|
|
|
func getTestUserWithHTTPFs() dataprovider.User {
|
|
u := getTestUser()
|
|
u.FsConfig.Provider = sdk.HTTPFilesystemProvider
|
|
u.FsConfig.HTTPConfig = vfs.HTTPFsConfig{
|
|
BaseHTTPFsConfig: sdk.BaseHTTPFsConfig{
|
|
Endpoint: fmt.Sprintf("http://127.0.0.1:%d/api/v1", httpFsPort),
|
|
Username: defaultHTTPFsUsername,
|
|
},
|
|
}
|
|
return u
|
|
}
|
|
|
|
func writeSFTPFile(name string, size int64, client *sftp.Client) error {
|
|
err := writeSFTPFileNoCheck(name, size, client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info, err := client.Stat(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.Size() != size {
|
|
return fmt.Errorf("file size mismatch, wanted %v, actual %v", size, info.Size())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeSFTPFileNoCheck(name string, size int64, client *sftp.Client) error {
|
|
content := make([]byte, size)
|
|
_, err := rand.Read(content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f, err := client.Create(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.Copy(f, bytes.NewBuffer(content))
|
|
if err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
return f.Close()
|
|
}
|
|
|
|
func getUploadScriptEnvContent(envVar string) []byte {
|
|
content := []byte("#!/bin/sh\n\n")
|
|
content = append(content, []byte(fmt.Sprintf("if [ -z \"$%s\" ]\n", envVar))...)
|
|
content = append(content, []byte("then\n")...)
|
|
content = append(content, []byte(" exit 1\n")...)
|
|
content = append(content, []byte("else\n")...)
|
|
content = append(content, []byte(" exit 0\n")...)
|
|
content = append(content, []byte("fi\n")...)
|
|
return content
|
|
}
|
|
|
|
func getUploadScriptContent(movedPath, logFilePath string, exitStatus int) []byte {
|
|
content := []byte("#!/bin/sh\n\n")
|
|
content = append(content, []byte("sleep 1\n")...)
|
|
if logFilePath != "" {
|
|
content = append(content, []byte(fmt.Sprintf("echo $@ > %v\n", logFilePath))...)
|
|
}
|
|
content = append(content, []byte(fmt.Sprintf("mv ${SFTPGO_ACTION_PATH} %v\n", movedPath))...)
|
|
content = append(content, []byte(fmt.Sprintf("exit %d", exitStatus))...)
|
|
return content
|
|
}
|
|
|
|
func getSaveProviderObjectScriptContent(outFilePath string, exitStatus int) []byte {
|
|
content := []byte("#!/bin/sh\n\n")
|
|
content = append(content, []byte(fmt.Sprintf("echo ${SFTPGO_OBJECT_DATA} > %v\n", outFilePath))...)
|
|
content = append(content, []byte(fmt.Sprintf("exit %d", exitStatus))...)
|
|
return content
|
|
}
|
|
|
|
func generateTOTPPasscode(secret string, algo otp.Algorithm) (string, error) {
|
|
return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
|
|
Period: 30,
|
|
Skew: 1,
|
|
Digits: otp.DigitsSix,
|
|
Algorithm: algo,
|
|
})
|
|
}
|
|
|
|
func isDbDefenderSupported() bool {
|
|
// SQLite shares the implementation with other SQL-based provider but it makes no sense
|
|
// to use it outside test cases
|
|
switch dataprovider.GetProviderStatus().Driver {
|
|
case dataprovider.MySQLDataProviderName, dataprovider.PGSQLDataProviderName,
|
|
dataprovider.CockroachDataProviderName, dataprovider.SQLiteDataProviderName:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func getEncryptedFileSize(size int64) (int64, error) {
|
|
encSize, err := sio.EncryptedSize(uint64(size))
|
|
return int64(encSize) + 33, err
|
|
}
|
|
|
|
func printLatestLogs(maxNumberOfLines int) {
|
|
var lines []string
|
|
f, err := os.Open(logFilePath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer f.Close()
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
lines = append(lines, scanner.Text()+"\r\n")
|
|
for len(lines) > maxNumberOfLines {
|
|
lines = lines[1:]
|
|
}
|
|
}
|
|
if scanner.Err() != nil {
|
|
logger.WarnToConsole("Unable to print latest logs: %v", scanner.Err())
|
|
return
|
|
}
|
|
for _, line := range lines {
|
|
logger.DebugToConsole("%s", line)
|
|
}
|
|
}
|
|
|
|
type receivedEmail struct {
|
|
sync.RWMutex
|
|
From string
|
|
To []string
|
|
Data string
|
|
}
|
|
|
|
func (e *receivedEmail) set(from string, to []string, data []byte) {
|
|
e.Lock()
|
|
defer e.Unlock()
|
|
|
|
e.From = from
|
|
e.To = to
|
|
e.Data = strings.ReplaceAll(string(data), "=\r\n", "")
|
|
}
|
|
|
|
func (e *receivedEmail) reset() {
|
|
e.Lock()
|
|
defer e.Unlock()
|
|
|
|
e.From = ""
|
|
e.To = nil
|
|
e.Data = ""
|
|
}
|
|
|
|
func (e *receivedEmail) get() receivedEmail {
|
|
e.RLock()
|
|
defer e.RUnlock()
|
|
|
|
return receivedEmail{
|
|
From: e.From,
|
|
To: e.To,
|
|
Data: e.Data,
|
|
}
|
|
}
|
|
|
|
func startHTTPFs() {
|
|
go func() {
|
|
readdirCallback := func(name string) []os.FileInfo {
|
|
if name == httpFsWellKnowDir {
|
|
return []os.FileInfo{vfs.NewFileInfo("ghost.txt", false, 0, time.Unix(0, 0), false)}
|
|
}
|
|
return nil
|
|
}
|
|
callbacks := &httpdtest.HTTPFsCallbacks{
|
|
Readdir: readdirCallback,
|
|
}
|
|
if err := httpdtest.StartTestHTTPFs(httpFsPort, callbacks); err != nil {
|
|
logger.ErrorToConsole("could not start HTTPfs test server: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
waitTCPListening(fmt.Sprintf(":%d", httpFsPort))
|
|
}
|