sftpgo/internal/sftpd/sftpd_test.go
Nicola Murino aa426016f2
sftpd: remove folder_prefix
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-04-26 11:43:25 +02:00

11851 lines
434 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 sftpd_test
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"hash"
"io"
"io/fs"
"math"
"net"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
_ "github.com/go-sql-driver/mysql"
_ "github.com/jackc/pgx/v5/stdlib"
_ "github.com/mattn/go-sqlite3"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"github.com/pkg/sftp"
"github.com/rs/zerolog"
"github.com/sftpgo/sdk"
sdkkms "github.com/sftpgo/sdk/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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/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/util"
"github.com/drakkan/sftpgo/v2/internal/vfs"
)
const (
logSender = "sftpdTesting"
sftpServerAddr = "127.0.0.1:2022"
sftpSrvAddr2222 = "127.0.0.1:2222"
defaultUsername = "test_user_sftp"
defaultPassword = "test_password"
defaultSFTPUsername = "test_sftpfs_user"
testPubKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1"
testPubKey1 = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCd60+/j+y8f0tLftihWV1YN9RSahMI9btQMDIMqts/jeNbD8jgoogM3nhF7KxfcaMKURuD47KC4Ey6iAJUJ0sWkSNNxOcIYuvA+5MlspfZDsa8Ag76Fe1vyz72WeHMHMeh/hwFo2TeIeIXg480T1VI6mzfDrVp2GzUx0SS0dMsQBjftXkuVR8YOiOwMCAH2a//M1OrvV7d/NBk6kBN0WnuIBb2jKm15PAA7+jQQG7tzwk2HedNH3jeL5GH31xkSRwlBczRK0xsCQXehAlx6cT/e/s44iJcJTHfpPKoSk6UAhPJYe7Z1QnuoawY9P9jQaxpyeImBZxxUEowhjpj2avBxKdRGBVK8R7EL8tSOeLbhdyWe5Mwc1+foEbq9Zz5j5Kd+hn3Wm1UnsGCrXUUUoZp1jnlNl0NakCto+5KmqnT9cHxaY+ix2RLUWAZyVFlRq71OYux1UHJnEJPiEI1/tr4jFBSL46qhQZv/TfpkfVW8FLz0lErfqu0gQEZnNHr3Fc= 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-----`
// password protected private key
testPrivateKeyPwd = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAvfwQQcs
+PyMsCLTNFcKiQAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILqltfCL7IPuIQ2q
+8w23flfgskjIlKViEwMfjJR4mrbAAAAkHp5xgG8J1XW90M/fT59ZUQht8sZzzP17rEKlX
waYKvLzDxkPK6LFIYs55W1EX1eVt/2Maq+zQ7k2SOUmhPNknsUOlPV2gytX3uIYvXF7u2F
FTBIJuzZ+UQ14wFbraunliE9yye9DajVG1kz2cz2wVgXUbee+gp5NyFVvln+TcTxXwMsWD
qwlk5iw/jQekxThg==
-----END OPENSSH PRIVATE KEY-----
`
testPubKeyPwd = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILqltfCL7IPuIQ2q+8w23flfgskjIlKViEwMfjJR4mrb"
privateKeyPwd = "password"
// test CA user key.
// % ssh-keygen -f ca_user_key
testCAUserKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDF5fcwZHiyixmnE6IlOZJpZhWXoh62gN+yadAA0GJ509SAEaZVLPDP8S5RsE8mUikR3wxynVshxHeqMhrkS+RlNbhSlOXDdNg94yTrq/xF8Z/PgKRInvef74k5i7bAIytza7jERzFJ/ujTEy3537T5k5EYQJ15ZQGuvzynSdv+6o99SjI4jFplyQOZ2QcYbEAmhHm5GgQlIiEFG/RlDtLksOulKZxOY3qPzP0AyQxtZJXn/5vG40aW9LTbwxCJqWlgrkFXMqAAVCbuU5YspwhiXmKt1PsldiXw23oloa4caCKN1jzbFiGuZNXEU2Ebx7JIvjQCPaUYwLjEbkRDxDqN/vmwZqBuKYiuG9Eafx+nFSQkr7QYb5b+mT+/1IFHnmeRGn38731kBqtH7tpzC/t+soRX9p2HtJM+9MYhblO2OqTSPGTlxihWUkyiRBekpAhaiHld16TsG+A3bOJHrojGcX+5g6oGarKGLAMcykL1X+rZqT993Mo6d2Z7q43MOXE= root@p1"
// this is testPubKey signed using testCAUserKey.
// % ssh-keygen -s ca_user_key -I test_user_sftp -n test_user_sftp -V always:forever -O source-address=127.0.0.1 -z 1 /tmp/test.pub
testCertValid = "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgm2fil1IIoTixrA2QE9tk7Vbspj/JdEY90e3K2htxYv8AAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0AAAAAAAAAAQAAAAEAAAAOdGVzdF91c2VyX3NmdHAAAAASAAAADnRlc3RfdXNlcl9zZnRwAAAAAAAAAAD//////////wAAACMAAAAOc291cmNlLWFkZHJlc3MAAAANAAAACTEyNy4wLjAuMQAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAMXl9zBkeLKLGacToiU5kmlmFZeiHraA37Jp0ADQYnnT1IARplUs8M/xLlGwTyZSKRHfDHKdWyHEd6oyGuRL5GU1uFKU5cN02D3jJOur/EXxn8+ApEie95/viTmLtsAjK3NruMRHMUn+6NMTLfnftPmTkRhAnXllAa6/PKdJ2/7qj31KMjiMWmXJA5nZBxhsQCaEebkaBCUiIQUb9GUO0uSw66UpnE5jeo/M/QDJDG1klef/m8bjRpb0tNvDEImpaWCuQVcyoABUJu5TliynCGJeYq3U+yV2JfDbeiWhrhxoIo3WPNsWIa5k1cRTYRvHski+NAI9pRjAuMRuREPEOo3++bBmoG4piK4b0Rp/H6cVJCSvtBhvlv6ZP7/UgUeeZ5EaffzvfWQGq0fu2nML+36yhFf2nYe0kz70xiFuU7Y6pNI8ZOXGKFZSTKJEF6SkCFqIeV3XpOwb4Dds4keuiMZxf7mDqgZqsoYsAxzKQvVf6tmpP33cyjp3Znurjcw5cQAAAZQAAAAMcnNhLXNoYTItNTEyAAABgMNenD7d1J9cF7JWgHA1DYpJ5+5knPtdXbbIgZAznsTxX7qOdptjeeYOuzhQ5Bwklh3fjewiJpGR1rBqbULP+6PAKeYqd7dNLH/upfKBfJweRf5pdXDpoknHaVuIhi4Uu6FeI4NkAzX9nqNKjFAflhJ+7GLGkLNb0UVZxgxr/t0rPmxc5iTg2ZRM+rk1Ij0S5RnGiKVsdAClqNA6h4TDzu5lJVdK5XvuNKBsKVRCvsVBOgJQTtRTLywQaqWR+HBfCiMj8X8EI7atDlJ6XIAlTLOO/f1sM8QPLjT0+tCHZaGFzg/lKPh3/yFQ4MvddZCptMy1Ll1xvj7cz2ynhGR4PiDfikV3YzgJU/KtL5y+ZB4jU08oPRiOP612PjwZZ+MqYOVOFCKUpMpZQs5UJHME+zNKr4LEj8M0x4YFKIciC+RsrCo4ujbJHmz61ionCadU+fmngvl3C3QjmUdgULBevODeUeIpJv4yFahNxrG1SKRTAa8VVDwJ9GdDTtmXM0mrwA== nicola@p1"
// this is testPubKey signed using a CA user key different from testCAUserKey
testCertUntrustedCA = "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg8oFPWpjYy/DowMmtOjWj7Dq20d2N/4Rxzr/c710tOOUAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0AAAAAAAAAAAAAAAEAAAAOdGVzdF91c2VyX3NmdHAAAAASAAAADnRlc3RfdXNlcl9zZnRwAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQCqgm2gVlptULThfpRR0oCb4SAU3368ULlJaiZOUdq6b94KTfgmu4hTLs7u3a8hyZnVxrKrJ93uAVCwa/HGtgiN96CNC6JUt/QnPqTJ8LQ207RdoE9fbOe6mGwOle5z45+5JFoIi5ZZuD8JsBGodVoa92UepoMyBcNtZyl9q2GP4yT2tIYRon79dtG9AXiDYyhSgePqaObN67dn3ivMc4ZGNukK3cG07cYPic5y0wxX16wSMG3pGQDyUkAu+s4AqpnV9EWHM4PE7SYkCXE99++tUK3QALYqvGZKrLHgzmDKi6n+e14vHYUppAeGDZzwlawiY4oGP9eOW2KUfjZe2ZeL22JTFDYzH2lNV2WtUpeKRGGTSGaUblRVC9hRt6hKCT4c7qpW4kO4kPhE39JpcNPGLql7srNkw+3xXBs8xghMPtH3nOl1Rz2mxnX5tAqmPBb+KiPepnrs+pBRu7i+nCVp8az+iN87STYHy+zPtvTR+QURC8BpNraPOfXwpwM2HaMAAAGUAAAADHJzYS1zaGEyLTUxMgAAAYBnTXCL6tXUO3/Gtsm7lnH9Sulzca8FOoI4Y/4bVYhq4iUNu7Ca452m+Xr9qmCEoIyIJF0LEEcJ8jcS4rfX15e7tNNoknv7JbYXBFAbp1Y/76iqVf89FjfVcbEyH2ToAf7eyQAWzQ3gEKS8mQIkLnAwmCboUXC4GRodSIiOXiTt5Q6T02MVc8TxkhmlTA0uVLd5XgstySgE/oLBnL59lhJcwQmdhHL+m480+PaW55CtMuC36RTwk/tOyuWCDC5qMXnoveNB3yu45o3L/U4hoyJ0/5FyP5C8ahgydY0LoRZQG/mNzuraY4433rK+IfkQvZTyaDtcjhxE6hCD5F40aDDh88i6XaKAPikD6fqra6BN8PoPgLuRHzOJuqsMXBWM99s7qPgSnBbmXlekz/1jvvFiCh3zvAFTxFz2KyE4+SbDcCrhpxkNL7idw6r/ZsHaI/2+zhDcgSs5MgBwYLJEj6zUqVdp5XsF8YfC7yNZV5/qy68qY2+zXrC57SPifU2SCPE= nicola@p1"
// this is testPubKey signed as host certificate.
// % ssh-keygen -s ca_user_key -I test_user_sftp -h -n test_user_sftp -V always:forever -z 2 /tmp/test.pub
testHostCert = "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg7O2LDpLO1jGTX3SSzEMILoAYJb9DdggyyaUMXUUg3L4AAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0AAAAAAAAAAgAAAAIAAAAOdGVzdF91c2VyX3NmdHAAAAASAAAADnRlc3RfdXNlcl9zZnRwAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAMXl9zBkeLKLGacToiU5kmlmFZeiHraA37Jp0ADQYnnT1IARplUs8M/xLlGwTyZSKRHfDHKdWyHEd6oyGuRL5GU1uFKU5cN02D3jJOur/EXxn8+ApEie95/viTmLtsAjK3NruMRHMUn+6NMTLfnftPmTkRhAnXllAa6/PKdJ2/7qj31KMjiMWmXJA5nZBxhsQCaEebkaBCUiIQUb9GUO0uSw66UpnE5jeo/M/QDJDG1klef/m8bjRpb0tNvDEImpaWCuQVcyoABUJu5TliynCGJeYq3U+yV2JfDbeiWhrhxoIo3WPNsWIa5k1cRTYRvHski+NAI9pRjAuMRuREPEOo3++bBmoG4piK4b0Rp/H6cVJCSvtBhvlv6ZP7/UgUeeZ5EaffzvfWQGq0fu2nML+36yhFf2nYe0kz70xiFuU7Y6pNI8ZOXGKFZSTKJEF6SkCFqIeV3XpOwb4Dds4keuiMZxf7mDqgZqsoYsAxzKQvVf6tmpP33cyjp3Znurjcw5cQAAAZQAAAAMcnNhLXNoYTItNTEyAAABgHlAWMTTzNrE6pxHlkr09ZXsHgJi8U2p7eifs56DOLgklYIXVUJPEEcnzMKGdpPBnqJsvg3+PccqxgOr5L1dFuOmekQ/dGiHd1enrESiGvJOvDfm0WsuBjxEZkSNFWgC9Z2NltToMmRlhVBmb4ZRZtAmi9DAFlJ/BDV4t8ikXZ5oUsigwIeOeLkdPFx3C3x9KZIpuwuAIV4Nfmz75q1NMWY2K1hv682QCKwMYqOWSotz1vWunNmZ0yPRl9UwqAq+nqwO3AApnlrQ3MmHujWQ5tl65PyhfpI8oghhUtB6YrJIAuRXNI/S0+KewCpiYm7nbFBtv9lpecujxAeTibYBrFZ5VODEUm3sdQ/HMdTmkhi6xNgPDQVlvKFqBJAaqoO3tbhKTbEZ865tJMqhyxmZ4XY08wduvSVobrNr7s3rm42/FXLIpung+UOVXonHyeIv9zQ0iJ/bvqKQ1fOsTisZdcD0lz80ZGsjdgJt7yKfUNBnAyVbTXm048E3WsZslJIYCA== nicola@p1"
// this is testPubKey signed using testCAUserKey but with source address 172.16.34.45.
// % ssh-keygen -s ca_user_key -I test_user_sftp -n test_user_sftp -V always:forever -O source-address=172.16.34.45 -z 3 /tmp/test.pub
testCertOtherSourceAddress = "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgZ4Su0250R4sQRNYJqJH9VTp9OyeYMAvqY5+lJRI4LzMAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0AAAAAAAAAAwAAAAEAAAAOdGVzdF91c2VyX3NmdHAAAAASAAAADnRlc3RfdXNlcl9zZnRwAAAAAAAAAAD//////////wAAACYAAAAOc291cmNlLWFkZHJlc3MAAAAQAAAADDE3Mi4xNi4zNC40NQAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAMXl9zBkeLKLGacToiU5kmlmFZeiHraA37Jp0ADQYnnT1IARplUs8M/xLlGwTyZSKRHfDHKdWyHEd6oyGuRL5GU1uFKU5cN02D3jJOur/EXxn8+ApEie95/viTmLtsAjK3NruMRHMUn+6NMTLfnftPmTkRhAnXllAa6/PKdJ2/7qj31KMjiMWmXJA5nZBxhsQCaEebkaBCUiIQUb9GUO0uSw66UpnE5jeo/M/QDJDG1klef/m8bjRpb0tNvDEImpaWCuQVcyoABUJu5TliynCGJeYq3U+yV2JfDbeiWhrhxoIo3WPNsWIa5k1cRTYRvHski+NAI9pRjAuMRuREPEOo3++bBmoG4piK4b0Rp/H6cVJCSvtBhvlv6ZP7/UgUeeZ5EaffzvfWQGq0fu2nML+36yhFf2nYe0kz70xiFuU7Y6pNI8ZOXGKFZSTKJEF6SkCFqIeV3XpOwb4Dds4keuiMZxf7mDqgZqsoYsAxzKQvVf6tmpP33cyjp3Znurjcw5cQAAAZQAAAAMcnNhLXNoYTItNTEyAAABgL34Q3Li8AJIxZLU+fh4i8ehUWpm31vEvlNjXVCeP70xI+5hWuEt6E1TgKw7GCL5GeD4KehX4vVcNs+A2eOdIUZfDBZIFxn88BN8xcMlDpAMJXgvNqGttiOwcspL6X3N288djUgpCI718lLRdz8nvFqcuYBhSpBm5KL4JzH5o1o8yqv75wMJsH8CJYwGhvWi0OgWOqaLRAk3IUxq3Fbgo/nX11NgrkY/dHIZCkGBFaLJ/M5mfmt/K/5hJAVgLcSxMwB/ryyGaziB9Pv7CwZ9uwnMoRcAvyr96lqgdtLt7LNY8ktugAJ7EnBWjQn4+EJAjjRK2sCaiwpdP37ckDZgmk0OWGEL1yVy8VXgl9QBd7Mb1EVl+lhRyw8jlgBXZOGqpdDrmKCdBYGtU7ujyndLXmxZEAlqhef0yCsyZPTkYH3RhjCYs8ATrEqndEpiL59Nej5uUGQURYijJfHep08AMb4rCxvIZATVm1Ocxu48rGCGolv8jZFJzSJq84HCrVRKMw== nicola@p1"
// this is testPubKey signed using testCAUserKey but expired.
// % ssh-keygen -s ca_user_key -I test_user_sftp -n test_user_sftp -V 20100101123000:20110101123000 -z 4 /tmp/test.pub
testCertExpired = "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgU3TLP5285k20fBSsdZioI78oJUpaRXFlgx5IPg6gWg8AAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0AAAAAAAAABAAAAAEAAAAOdGVzdF91c2VyX3NmdHAAAAASAAAADnRlc3RfdXNlcl9zZnRwAAAAAEs93LgAAAAATR8QOAAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQDF5fcwZHiyixmnE6IlOZJpZhWXoh62gN+yadAA0GJ509SAEaZVLPDP8S5RsE8mUikR3wxynVshxHeqMhrkS+RlNbhSlOXDdNg94yTrq/xF8Z/PgKRInvef74k5i7bAIytza7jERzFJ/ujTEy3537T5k5EYQJ15ZQGuvzynSdv+6o99SjI4jFplyQOZ2QcYbEAmhHm5GgQlIiEFG/RlDtLksOulKZxOY3qPzP0AyQxtZJXn/5vG40aW9LTbwxCJqWlgrkFXMqAAVCbuU5YspwhiXmKt1PsldiXw23oloa4caCKN1jzbFiGuZNXEU2Ebx7JIvjQCPaUYwLjEbkRDxDqN/vmwZqBuKYiuG9Eafx+nFSQkr7QYb5b+mT+/1IFHnmeRGn38731kBqtH7tpzC/t+soRX9p2HtJM+9MYhblO2OqTSPGTlxihWUkyiRBekpAhaiHld16TsG+A3bOJHrojGcX+5g6oGarKGLAMcykL1X+rZqT993Mo6d2Z7q43MOXEAAAGUAAAADHJzYS1zaGEyLTUxMgAAAYAlH3hhj8J6xLyVpeLZjblzwDKrxp/MWiH30hQ965ExPrPRcoAZFEKVqOYdj6bp4Q19Q4Yzqdobg3aN5ym2iH0b2TlOY0mM901CAoHbNJyiLs+0KiFRoJ+30EDj/hcKusg6v8ln2yixPagAyQu3zyiWo4t1ZuO3I86xchGlptStxSdHAHPFCfpbhcnzWFZctiMqUutl82C4ROWyjOZcRzdVdWHeN5h8wnooXuvba2VkT8QPmjYYyRGuQ3Hg+ySdh8Tel4wiix1Dg5MX7Wjh4hKEx80No9UPy+0iyZMNc07lsWAtrY6NRxGM5CzB6mklscB8TzFrVSnIl9u3bquLfaCrFt/Mft5dR7Yy4jmF+zUhjia6h6giCZ91J+FZ4hV+WkBtPCvTfrGWoA1BgEB/iI2xOq/NPqJ7UXRoMXk/l0NPgRPT2JS1adegqnt4ddr6IlmPyZxaSEvXhanjKdfMlEFYO1wz7ouqpYUozQVy4KXBlzFlNwyD1hI+k4+/A6AIYeI= nicola@p1"
// this is testPubKey signed without a principal
// ssh-keygen -s ca_user_key -I test_user_sftp -V always:forever -O source-address=127.0.0.1 -z 1 /tmp/test.pub
testCertNoPrincipals = "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg2Bx0s8nafJtriqoBuQfbFByhdQMkjDIZhV90JZSGN8AAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0AAAAAAAAAAQAAAAEAAAAOdGVzdF91c2VyX3NmdHAAAAAAAAAAAAAAAAD//////////wAAACMAAAAOc291cmNlLWFkZHJlc3MAAAANAAAACTEyNy4wLjAuMQAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAMXl9zBkeLKLGacToiU5kmlmFZeiHraA37Jp0ADQYnnT1IARplUs8M/xLlGwTyZSKRHfDHKdWyHEd6oyGuRL5GU1uFKU5cN02D3jJOur/EXxn8+ApEie95/viTmLtsAjK3NruMRHMUn+6NMTLfnftPmTkRhAnXllAa6/PKdJ2/7qj31KMjiMWmXJA5nZBxhsQCaEebkaBCUiIQUb9GUO0uSw66UpnE5jeo/M/QDJDG1klef/m8bjRpb0tNvDEImpaWCuQVcyoABUJu5TliynCGJeYq3U+yV2JfDbeiWhrhxoIo3WPNsWIa5k1cRTYRvHski+NAI9pRjAuMRuREPEOo3++bBmoG4piK4b0Rp/H6cVJCSvtBhvlv6ZP7/UgUeeZ5EaffzvfWQGq0fu2nML+36yhFf2nYe0kz70xiFuU7Y6pNI8ZOXGKFZSTKJEF6SkCFqIeV3XpOwb4Dds4keuiMZxf7mDqgZqsoYsAxzKQvVf6tmpP33cyjp3Znurjcw5cQAAAZQAAAAMcnNhLXNoYTItNTEyAAABgHgax/++NA5YZXDHH180BcQtDBve8Vc+XJzqQUe8xBiqd+KJnas6He7vW62qMaAfu63i0Uycj2Djfjy5dyx1GB9wup8YuP5mXlmJTx+7UPPjwbfrZWtk8iJ7KhFAwjh0KRZD4uIvoeecK8QE9zh64k2LNVqlWbFTdoPulRC29cGcXDpMU2eToFEyWbceHOZyyifXf98ZMZbaQzWzwSZ5rFucJ1b0aeT6aAJWB+Dq7mIQWf/jCWr8kNaeCzMKJsFQkQEfmHls29ChV92sNRhngUDxll0Ir0wpPea1fFEBnUhLRTLC8GhDDbWAzsZtXqx9fjoAkb/gwsU6TGxevuOMxEABjDA9PyJiTXJI9oTUCwDIAUVVFLsCEum3o/BblngXajUGibaif5ZSKBocpP70oTeAngQYB7r1/vquQzGsGFhTN4FUXLSpLu9Zqi1z58/qa7SgKSfNp98X/4zrhltAX73ZEvg0NUMv2HwlwlqHdpF3FYolAxInp7c2jBTncQ2l3w== nicola@p1"
osWindows = "windows"
testFileName = "test_file_sftp.dat"
testDLFileName = "test_download_sftp.dat"
)
var (
configDir = filepath.Join(".", "..", "..")
allPerms = []string{dataprovider.PermAny}
homeBasePath string
scpPath string
scpForce bool
gitPath string
sshPath string
hookCmdPath string
pubKeyPath string
privateKeyPath string
trustedCAUserKey string
revokeUserCerts string
gitWrapPath string
extAuthPath string
keyIntAuthPath string
preLoginPath string
postConnectPath string
preDownloadPath string
preUploadPath string
checkPwdPath string
logFilePath string
hostKeyFPs []string
)
func TestMain(m *testing.M) {
logFilePath = filepath.Join(configDir, "sftpgo_sftpd_test.log")
loginBannerFileName := "login_banner"
loginBannerFile := filepath.Join(configDir, loginBannerFileName)
logger.InitLogger(logFilePath, 10, 1, 28, false, false, zerolog.DebugLevel)
err := os.WriteFile(loginBannerFile, []byte("simple login banner\n"), os.ModePerm)
if err != nil {
logger.ErrorToConsole("error creating login banner: %v", err)
}
os.Setenv("SFTPGO_COMMON__UPLOAD_MODE", "2")
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()
logger.InfoToConsole("Starting SFTPD tests, provider: %v", providerConf.Driver)
commonConf := config.GetCommonConfig()
homeBasePath = os.TempDir()
checkSystemCommands()
var scriptArgs string
if runtime.GOOS == osWindows {
scriptArgs = "%*"
} else {
commonConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete", "ssh_cmd",
"pre-download", "pre-upload"}
commonConf.Actions.Hook = hookCmdPath
scriptArgs = "$@"
}
err = dataprovider.Initialize(providerConf, configDir, true)
if err != nil {
logger.ErrorToConsole("error initializing data provider: %v", err)
os.Exit(1)
}
err = dataprovider.UpdateConfigs(nil, "", "", "")
if err != nil {
logger.ErrorToConsole("error resetting configs: %v", err)
os.Exit(1)
}
err = common.Initialize(commonConf, 0)
if err != nil {
logger.WarnToConsole("error initializing common: %v", err)
os.Exit(1)
}
httpConfig := config.GetHTTPConfig()
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()
httpdConf := config.GetHTTPDConfig()
sftpdConf.Bindings = []sftpd.Binding{
{
Port: 2022,
ApplyProxyConfig: true,
},
}
sftpdConf.KexAlgorithms = []string{"curve25519-sha256@libssh.org", ssh.KeyExchangeECDHP256,
ssh.KeyExchangeECDHP384}
sftpdConf.Ciphers = []string{ssh.CipherChacha20Poly1305, ssh.CipherAES128GCM,
ssh.CipherAES256CTR}
sftpdConf.LoginBannerFile = loginBannerFileName
// we need to test all supported ssh commands
sftpdConf.EnabledSSHCommands = []string{"*"}
keyIntAuthPath = filepath.Join(homeBasePath, "keyintauth.sh")
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, 1), os.ModePerm)
if err != nil {
logger.ErrorToConsole("error writing keyboard interactive script: %v", err)
os.Exit(1)
}
sftpdConf.KeyboardInteractiveAuthentication = true
sftpdConf.KeyboardInteractiveHook = keyIntAuthPath
createInitialFiles(scriptArgs)
sftpdConf.TrustedUserCAKeys = append(sftpdConf.TrustedUserCAKeys, trustedCAUserKey)
sftpdConf.RevokedUserCertsFile = revokeUserCerts
go func(cfg sftpd.Configuration) {
logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
if err := cfg.Initialize(configDir); err != nil {
logger.ErrorToConsole("could not start SFTP server: %v", err)
os.Exit(1)
}
}(sftpdConf)
go func() {
if err := httpdConf.Initialize(configDir, 0); err != nil {
logger.ErrorToConsole("could not start HTTP server: %v", err)
os.Exit(1)
}
}()
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
waitTCPListening(httpdConf.Bindings[0].GetAddress())
sftpdConf.Bindings = []sftpd.Binding{
{
Port: 2222,
ApplyProxyConfig: true,
},
}
sftpdConf.PasswordAuthentication = false
common.Config.ProxyProtocol = 1
go func(cfg sftpd.Configuration) {
logger.Debug(logSender, "", "initializing SFTP server with config %+v and proxy protocol %v",
sftpdConf, common.Config.ProxyProtocol)
if err := cfg.Initialize(configDir); err != nil {
logger.ErrorToConsole("could not start SFTP server with proxy protocol 1: %v", err)
os.Exit(1)
}
}(sftpdConf)
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
sftpdConf.Bindings = []sftpd.Binding{
{
Port: 2226,
ApplyProxyConfig: false,
},
}
sftpdConf.PasswordAuthentication = true
go func(cfg sftpd.Configuration) {
logger.Debug(logSender, "", "initializing SFTP server with config %+v and proxy protocol %v",
cfg, common.Config.ProxyProtocol)
if err := cfg.Initialize(configDir); err != nil {
logger.ErrorToConsole("could not start SFTP server with proxy protocol 2: %v", err)
os.Exit(1)
}
}(sftpdConf)
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
sftpdConf.Bindings = []sftpd.Binding{
{
Port: 2224,
ApplyProxyConfig: true,
},
}
sftpdConf.PasswordAuthentication = true
common.Config.ProxyProtocol = 2
go func() {
logger.Debug(logSender, "", "initializing SFTP server with config %+v and proxy protocol %v",
sftpdConf, common.Config.ProxyProtocol)
if err := sftpdConf.Initialize(configDir); err != nil {
logger.ErrorToConsole("could not start SFTP server with proxy protocol 2: %v", err)
os.Exit(1)
}
}()
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
getHostKeysFingerprints(sftpdConf.HostKeys)
startHTTPFs()
exitCode := m.Run()
os.Remove(logFilePath)
os.Remove(loginBannerFile)
os.Remove(pubKeyPath)
os.Remove(privateKeyPath)
os.Remove(trustedCAUserKey)
os.Remove(revokeUserCerts)
os.Remove(gitWrapPath)
os.Remove(extAuthPath)
os.Remove(preLoginPath)
os.Remove(postConnectPath)
os.Remove(preDownloadPath)
os.Remove(preUploadPath)
os.Remove(keyIntAuthPath)
os.Remove(checkPwdPath)
os.Exit(exitCode)
}
func TestInitialization(t *testing.T) {
err := config.LoadConfig(configDir, "")
assert.NoError(t, err)
sftpdConf := config.GetSFTPDConfig()
sftpdConf.Bindings = []sftpd.Binding{
{
Port: 2022,
ApplyProxyConfig: true,
},
{
Port: 0,
},
}
sftpdConf.LoginBannerFile = "invalid_file"
sftpdConf.EnabledSSHCommands = append(sftpdConf.EnabledSSHCommands, "ls")
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.KeyboardInteractiveAuthentication = true
sftpdConf.KeyboardInteractiveHook = "invalid_file"
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.KeyboardInteractiveAuthentication = true
sftpdConf.KeyboardInteractiveHook = filepath.Join(homeBasePath, "invalid_file")
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.KeyboardInteractiveAuthentication = false
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.Bindings = []sftpd.Binding{
{
Port: 4444,
ApplyProxyConfig: true,
},
}
common.Config.ProxyProtocol = 1
assert.True(t, sftpdConf.Bindings[0].HasProxy())
common.Config.ProxyProtocol = 0
sftpdConf.HostKeys = []string{"missing key"}
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.HostKeys = nil
sftpdConf.TrustedUserCAKeys = []string{"missing ca key"}
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.Bindings = nil
err = sftpdConf.Initialize(configDir)
assert.EqualError(t, err, common.ErrNoBinding.Error())
sftpdConf = config.GetSFTPDConfig()
sftpdConf.Ciphers = []string{"not a cipher"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported cipher")
}
sftpdConf.Ciphers = nil
sftpdConf.MACs = []string{"not a MAC"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported MAC algorithm")
}
sftpdConf.MACs = nil
sftpdConf.KexAlgorithms = []string{"diffie-hellman-group-exchange-sha1", "not a KEX"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported key-exchange algorithm")
}
sftpdConf.KexAlgorithms = nil
sftpdConf.PublicKeyAlgorithms = []string{"not a pub key algo"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported public key authentication algorithm")
}
sftpdConf.PublicKeyAlgorithms = nil
sftpdConf.HostKeyAlgorithms = []string{"not a host key algo"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unsupported host key algorithm")
}
sftpdConf.HostKeyAlgorithms = nil
sftpdConf.HostCertificates = []string{"missing file"}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unable to load host certificate")
}
sftpdConf.HostCertificates = []string{"."}
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
hostCertPath := filepath.Join(os.TempDir(), "host_cert.pub")
err = os.WriteFile(hostCertPath, []byte(testCertValid), 0600)
assert.NoError(t, err)
sftpdConf.HostKeys = []string{privateKeyPath}
sftpdConf.HostCertificates = []string{hostCertPath}
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "is not an host certificate")
}
err = os.WriteFile(hostCertPath, []byte(testPubKey), 0600)
assert.NoError(t, err)
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "is not an SSH certificate")
}
err = os.WriteFile(hostCertPath, []byte("abc"), 0600)
assert.NoError(t, err)
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unable to parse host certificate")
}
err = os.WriteFile(hostCertPath, []byte(testHostCert), 0600)
assert.NoError(t, err)
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
err = os.Remove(hostCertPath)
assert.NoError(t, err)
sftpdConf.HostKeys = nil
sftpdConf.HostCertificates = nil
sftpdConf.RevokedUserCertsFile = "."
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.RevokedUserCertsFile = "a missing file"
err = sftpdConf.Initialize(configDir)
assert.ErrorIs(t, err, os.ErrNotExist)
err = createTestFile(revokeUserCerts, 10*1024*1024)
assert.NoError(t, err)
sftpdConf.RevokedUserCertsFile = revokeUserCerts
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
err = os.WriteFile(revokeUserCerts, []byte(`[]`), 0644)
assert.NoError(t, err)
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
err = dataprovider.Close()
assert.NoError(t, err)
err = sftpdConf.Initialize(configDir)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unable to load configs from provider")
}
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
}
func TestBasicSFTPHandling(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaSize = 6553600
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, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
expectedQuotaSize := user.UsedQuotaSize + testFileSize
expectedQuotaFiles := user.UsedQuotaFiles + 1
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join("/missing_dir", testFileName), testFileSize, client)
assert.Error(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, int64(0), user.FirstUpload)
assert.Equal(t, int64(0), user.FirstDownload)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.FirstUpload, int64(0))
assert.Equal(t, int64(0), user.FirstDownload)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
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 = client.Remove(testFileName)
assert.NoError(t, err)
_, err = client.Lstat(testFileName)
assert.Error(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles-1, user.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize-testFileSize, user.UsedQuotaSize)
assert.Greater(t, user.FirstUpload, int64(0))
assert.Greater(t, user.FirstDownload, int64(0))
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
u.Username = "missing user"
_, _, err = getSftpClient(u, false)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
status := sftpd.GetStatus()
assert.True(t, status.IsActive)
sshCommands := status.GetSSHCommandsAsString()
assert.NotEmpty(t, sshCommands)
sshAuths := status.GetSupportedAuthsAsString()
assert.NotEmpty(t, sshAuths)
assert.NotEmpty(t, status.HostKeys[0].GetAlgosAsString())
assert.NotEmpty(t, status.GetMACsAsString())
assert.NotEmpty(t, status.GetKEXsAsString())
assert.NotEmpty(t, status.GetCiphersAsString())
assert.NotEmpty(t, status.GetPublicKeysAlgosAsString())
}
func TestBasicSFTPFsHandling(t *testing.T) {
usePubKey := true
baseUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
u := getTestSFTPUser(usePubKey)
u.QuotaSize = 6553600
u.FsConfig.SFTPConfig.DisableCouncurrentReads = true
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
testLinkName := testFileName + ".link"
testLinkToLinkName := testLinkName + ".link"
expectedQuotaSize := testFileSize
expectedQuotaFiles := 1
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
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 = client.Symlink(testFileName, testLinkName)
assert.NoError(t, err)
info, err := client.Lstat(testLinkName)
if assert.NoError(t, err) {
assert.True(t, info.Mode()&os.ModeSymlink != 0)
}
info, err = client.Stat(testLinkName)
if assert.NoError(t, err) {
assert.True(t, info.Mode()&os.ModeSymlink == 0)
}
val, err := client.ReadLink(testLinkName)
if assert.NoError(t, err) {
assert.Equal(t, path.Join("/", testFileName), val)
}
linkDir := "linkDir"
err = client.Mkdir(linkDir)
assert.NoError(t, err)
linkToLinkPath := path.Join(linkDir, testLinkToLinkName)
err = client.Symlink(path.Join("/", testLinkName), linkToLinkPath)
assert.NoError(t, err)
info, err = client.Lstat(linkToLinkPath)
if assert.NoError(t, err) {
assert.True(t, info.Mode()&os.ModeSymlink != 0)
}
info, err = client.Stat(linkToLinkPath)
if assert.NoError(t, err) {
assert.True(t, info.Mode()&os.ModeSymlink == 0)
}
val, err = client.ReadLink(linkToLinkPath)
if assert.NoError(t, err) {
assert.Equal(t, path.Join("/", testLinkName), val)
}
err = client.Remove(linkToLinkPath)
assert.NoError(t, err)
err = client.RemoveDirectory(linkDir)
assert.NoError(t, err)
err = client.Remove(testFileName)
assert.NoError(t, err)
_, err = client.Lstat(testFileName)
assert.Error(t, err)
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)
// now overwrite the symlink
err = sftpUploadFile(testFilePath, testLinkName, testFileSize, client)
assert.NoError(t, err)
contents, err := client.ReadDir("/")
if assert.NoError(t, err) {
assert.Len(t, contents, 1)
assert.Equal(t, testFileSize, contents[0].Size())
assert.Equal(t, testLinkName, contents[0].Name())
assert.False(t, contents[0].IsDir())
assert.True(t, contents[0].Mode().IsRegular())
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
stat, err := client.StatVFS("/")
assert.NoError(t, err)
assert.Equal(t, uint64(u.QuotaSize/4096), stat.Blocks)
assert.Equal(t, uint64((u.QuotaSize-testFileSize)/4096), stat.Bfree)
assert.Equal(t, uint64(1), stat.Files-stat.Ffree)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(baseUser.GetHomeDir())
assert.NoError(t, err)
}
func TestSFTPFsPasswordProtectedPrivateKey(t *testing.T) {
usePubKey := false
u := getTestUser(true)
u.PublicKeys = []string{testPubKeyPwd}
baseUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(testPrivateKeyPwd)
u.FsConfig.SFTPConfig.KeyPassphrase = kms.NewPlainSecret(privateKeyPwd)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
// update the user, the key must be preserved
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
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 = httpdtest.RemoveUser(baseUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(baseUser.GetHomeDir())
assert.NoError(t, err)
}
func TestSFTPFsEscapeHomeDir(t *testing.T) {
usePubKey := true
baseUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
u := getTestSFTPUser(usePubKey)
sftpPrefix := "/prefix"
u.FsConfig.SFTPConfig.Prefix = sftpPrefix
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
dirName := "dir"
linkName := "link"
err := client.Mkdir(dirName)
assert.NoError(t, err)
err = os.Symlink(baseUser.GetHomeDir(), filepath.Join(baseUser.GetHomeDir(), sftpPrefix, dirName, linkName))
assert.NoError(t, err)
err = os.Symlink(filepath.Join(baseUser.GetHomeDir(), sftpPrefix, dirName, linkName),
filepath.Join(baseUser.GetHomeDir(), sftpPrefix, linkName))
assert.NoError(t, err)
// linkName points to a link inside the home dir and this link points to a dir outside the home dir
_, err = client.ReadLink(linkName)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.RealPath(linkName)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.ReadDir(linkName)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.ReadDir(path.Join(dirName, linkName))
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.ReadDir("/")
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(baseUser.GetHomeDir())
assert.NoError(t, err)
}
func TestGroupSettingsOverride(t *testing.T) {
usePubKey := true
g := getTestGroup()
g.UserSettings.Filters.StartDirectory = "/%username%"
group, _, err := httpdtest.AddGroup(g, http.StatusCreated)
assert.NoError(t, err)
u := getTestUser(usePubKey)
u.Groups = []sdk.GroupMapping{
{
Name: group.Name,
Type: sdk.GroupTypePrimary,
},
}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
currentDir, err := client.Getwd()
assert.NoError(t, err)
assert.Equal(t, "/"+user.Username, currentDir)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveGroup(group, http.StatusOK)
assert.NoError(t, err)
}
func TestStartDirectory(t *testing.T) {
usePubKey := false
startDir := "/st@ rt/dir"
u := getTestUser(usePubKey)
u.Filters.StartDirectory = startDir
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.Filters.StartDirectory = startDir
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
currentDir, err := client.Getwd()
assert.NoError(t, err)
assert.Equal(t, startDir, currentDir)
entries, err := client.ReadDir(".")
assert.NoError(t, err)
assert.Len(t, entries, 0)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
_, err = client.Stat(testFileName)
assert.NoError(t, err)
err = client.Rename(testFileName, testFileName+"_rename")
assert.NoError(t, err)
entries, err = client.ReadDir(".")
assert.NoError(t, err)
assert.Len(t, entries, 1)
currentDir, err = client.RealPath("..")
assert.NoError(t, err)
assert.Equal(t, path.Dir(startDir), currentDir)
currentDir, err = client.RealPath("../..")
assert.NoError(t, err)
assert.Equal(t, "/", currentDir)
currentDir, err = client.RealPath("../../..")
assert.NoError(t, err)
assert.Equal(t, "/", currentDir)
err = client.Remove(testFileName + "_rename")
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
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)
}
func TestLoginNonExistentUser(t *testing.T) {
usePubKey := true
user := getTestUser(usePubKey)
_, _, err := getSftpClient(user, usePubKey)
assert.Error(t, err)
}
func TestRateLimiter(t *testing.T) {
oldConfig := config.GetCommonConfig()
cfg := config.GetCommonConfig()
cfg.RateLimitersConfig = []common.RateLimiterConfig{
{
Average: 1,
Period: 1000,
Burst: 1,
Type: 1,
Protocols: []string{common.ProtocolSSH},
},
}
err := common.Initialize(cfg, 0)
assert.NoError(t, err)
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = common.Initialize(oldConfig, 0)
assert.NoError(t, err)
}
func TestDefender(t *testing.T) {
oldConfig := config.GetCommonConfig()
cfg := config.GetCommonConfig()
cfg.DefenderConfig.Enabled = true
cfg.DefenderConfig.Threshold = 3
cfg.DefenderConfig.ScoreLimitExceeded = 2
cfg.DefenderConfig.ScoreValid = 1
err := common.Initialize(cfg, 0)
assert.NoError(t, err)
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
user.Password = "wrong_pwd"
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
hosts, _, err := httpdtest.GetDefenderHosts(http.StatusOK)
assert.NoError(t, err)
if assert.Len(t, hosts, 1) {
host := hosts[0]
assert.Empty(t, host.GetBanTime())
assert.Equal(t, 1, host.Score)
}
for i := 0; i < 2; i++ {
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
}
user.Password = defaultPassword
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
err = dataprovider.DeleteUser(user.Username, "", "", "")
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = common.Initialize(oldConfig, 0)
assert.NoError(t, err)
}
func TestOpenReadWrite(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaSize = 6553600
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.QuotaSize = 6553600
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
sftpFile, err := client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("sample test data")
n, err := sftpFile.Write(testData)
assert.NoError(t, err)
assert.Equal(t, len(testData), n)
buffer := make([]byte, 128)
n, err = sftpFile.ReadAt(buffer, 1)
assert.EqualError(t, err, io.EOF.Error())
assert.Equal(t, len(testData)-1, n)
assert.Equal(t, testData[1:], buffer[:n])
err = sftpFile.Close()
assert.NoError(t, err)
}
sftpFile, err = client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("new test data")
n, err := sftpFile.Write(testData)
assert.NoError(t, err)
assert.Equal(t, len(testData), n)
buffer := make([]byte, 128)
n, err = sftpFile.ReadAt(buffer, 1)
assert.EqualError(t, err, io.EOF.Error())
assert.Equal(t, len(testData)-1, n)
assert.Equal(t, testData[1:], buffer[:n])
err = sftpFile.Close()
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)
}
func TestOpenReadWritePerm(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
// we cannot read inside "/sub", rename is needed otherwise the atomic upload will fail for the sftpfs user
u.Permissions["/sub"] = []string{dataprovider.PermUpload, dataprovider.PermListItems, dataprovider.PermRename}
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.Permissions["/sub"] = []string{dataprovider.PermUpload, dataprovider.PermListItems}
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("sub")
assert.NoError(t, err)
sftpFileName := path.Join("sub", "file.txt")
sftpFile, err := client.OpenFile(sftpFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("test data")
n, err := sftpFile.Write(testData)
assert.NoError(t, err)
assert.Equal(t, len(testData), n)
buffer := make([]byte, 128)
_, err = sftpFile.ReadAt(buffer, 1)
if assert.Error(t, err) {
assert.Contains(t, strings.ToLower(err.Error()), "permission denied")
}
err = sftpFile.Close()
assert.NoError(t, err)
}
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
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)
}
func TestConcurrency(t *testing.T) {
oldValue := common.Config.MaxPerHostConnections
common.Config.MaxPerHostConnections = 0
usePubKey := true
numLogins := 50
u := getTestUser(usePubKey)
u.QuotaFiles = numLogins + 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
var wg sync.WaitGroup
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(262144)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
var closedConns atomic.Int32
for i := 0; i < numLogins; i++ {
wg.Add(1)
go func(counter int) {
defer wg.Done()
defer closedConns.Add(1)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
err = sftpUploadFile(testFilePath, testFileName+strconv.Itoa(counter), testFileSize, client)
assert.NoError(t, err)
assert.Greater(t, common.Connections.GetActiveSessions(defaultUsername), 0)
client.Close()
conn.Close()
}
}(i)
}
wg.Add(1)
go func() {
defer wg.Done()
maxConns := 0
maxSessions := 0
for {
servedReqs := closedConns.Load()
if servedReqs > 0 {
stats := common.Connections.GetStats("")
if len(stats) > maxConns {
maxConns = len(stats)
}
activeSessions := common.Connections.GetActiveSessions(defaultUsername)
if activeSessions > maxSessions {
maxSessions = activeSessions
}
}
if servedReqs >= int32(numLogins) {
break
}
time.Sleep(1 * time.Millisecond)
}
assert.Greater(t, maxConns, 0)
assert.Greater(t, maxSessions, 0)
}()
wg.Wait()
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
files, err := client.ReadDir(".")
assert.NoError(t, err)
assert.Len(t, files, numLogins)
client.Close()
conn.Close()
}
assert.Eventually(t, func() bool {
return common.Connections.GetActiveSessions(defaultUsername) == 0
}, 1*time.Second, 50*time.Millisecond)
assert.Eventually(t, func() bool {
return len(common.Connections.GetStats("")) == 0
}, 1*time.Second, 50*time.Millisecond)
err = os.Remove(testFilePath)
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.MaxPerHostConnections = oldValue
}
func TestProxyProtocol(t *testing.T) {
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
// remove the home dir to test auto creation
err = os.RemoveAll(user.HomeDir)
assert.NoError(t, err)
conn, client, err := getSftpClientWithAddr(user, usePubKey, sftpSrvAddr2222)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
conn, client, err = getSftpClientWithAddr(user, usePubKey, "127.0.0.1:2224")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestRealPath(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
p, err := client.RealPath("../..")
assert.NoError(t, err)
assert.Equal(t, "/", p)
p, err = client.RealPath("../test")
assert.NoError(t, err)
assert.Equal(t, "/test", p)
subdir := "testsubdir"
err = client.Mkdir(subdir)
assert.NoError(t, err)
linkName := testFileName + "_link"
err = client.Symlink(path.Join("/", testFileName), path.Join(subdir, linkName))
assert.NoError(t, err)
p, err = client.RealPath(path.Join(subdir, linkName))
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testFileName), p)
// an existing path
sftpFile, err := client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("hello world")
n, err := sftpFile.WriteAt(testData, 0)
assert.NoError(t, err)
assert.Equal(t, len(testData), n)
}
p, err = client.RealPath(path.Join(subdir, linkName))
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testFileName), p)
// now a link outside the home dir
err = os.Symlink(filepath.Clean(os.TempDir()), filepath.Join(localUser.GetHomeDir(), subdir, "temp"))
assert.NoError(t, err)
_, err = client.RealPath(path.Join(subdir, "temp"))
assert.ErrorIs(t, err, os.ErrPermission)
conn.Close()
client.Close()
err = os.Remove(filepath.Join(localUser.GetHomeDir(), subdir, "temp"))
assert.NoError(t, err)
if user.Username == localUser.Username {
err = os.RemoveAll(filepath.Join(localUser.GetHomeDir(), subdir))
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)
}
func TestBufferedSFTP(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.FsConfig.SFTPConfig.BufferSize = 2
u.HomeDir = filepath.Join(os.TempDir(), u.Username)
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(sftpUser, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
appendDataSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
initialHash, err := computeHashForFile(sha256.New(), testFilePath)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = appendToTestFile(testFilePath, appendDataSize)
assert.NoError(t, err)
err = sftpUploadResumeFile(testFilePath, testFileName, testFileSize+appendDataSize, false, client)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
downloadedFileHash, err := computeHashForFile(sha256.New(), localDownloadPath)
assert.NoError(t, err)
assert.Equal(t, initialHash, downloadedFileHash)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
sftpFile, err := client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("sample test sftp data")
n, err := sftpFile.Write(testData)
assert.NoError(t, err)
assert.Equal(t, len(testData), n)
err = sftpFile.Truncate(0)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}
err = sftpFile.Truncate(4)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}
buffer := make([]byte, 128)
_, err = sftpFile.Read(buffer)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}
err = sftpFile.Close()
assert.NoError(t, err)
info, err := client.Stat(testFileName)
if assert.NoError(t, err) {
assert.Equal(t, int64(len(testData)), info.Size())
}
}
// test WriteAt
sftpFile, err = client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("hello world")
n, err := sftpFile.WriteAt(testData[:6], 0)
assert.NoError(t, err)
assert.Equal(t, 6, n)
n, err = sftpFile.WriteAt(testData[6:], 6)
assert.NoError(t, err)
assert.Equal(t, 5, n)
err = sftpFile.Close()
assert.NoError(t, err)
info, err := client.Stat(testFileName)
if assert.NoError(t, err) {
assert.Equal(t, int64(len(testData)), info.Size())
}
}
// test ReadAt
sftpFile, err = client.OpenFile(testFileName, os.O_RDONLY)
if assert.NoError(t, err) {
buffer := make([]byte, 128)
n, err := sftpFile.ReadAt(buffer, 6)
assert.ErrorIs(t, err, io.EOF)
assert.Equal(t, 5, n)
assert.Equal(t, []byte("world"), buffer[:n])
err = sftpFile.Close()
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 = os.RemoveAll(sftpUser.GetHomeDir())
assert.NoError(t, err)
}
func TestUploadResume(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestUser(usePubKey)
u.FsConfig.OSConfig = sdk.OSFsConfig{
WriteBufferSize: 1,
ReadBufferSize: 1,
}
u.Username += "_buffered"
u.HomeDir += "_with_buf"
bufferedUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser, bufferedUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
appendDataSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = appendToTestFile(testFilePath, appendDataSize)
assert.NoError(t, err)
err = sftpUploadResumeFile(testFilePath, testFileName, testFileSize+appendDataSize, false, client)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize+appendDataSize, client)
assert.NoError(t, err)
initialHash, err := computeHashForFile(sha256.New(), testFilePath)
assert.NoError(t, err)
downloadedFileHash, err := computeHashForFile(sha256.New(), localDownloadPath)
assert.NoError(t, err)
assert.Equal(t, initialHash, downloadedFileHash)
err = sftpUploadResumeFile(testFilePath, testFileName, testFileSize+appendDataSize, true, client)
assert.Error(t, err, "resume uploading file with invalid offset must fail")
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
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 = httpdtest.RemoveUser(bufferedUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(bufferedUser.GetHomeDir())
assert.NoError(t, err)
}
func TestDirCommands(t *testing.T) {
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
// remove the home dir to test auto creation
err = os.RemoveAll(user.HomeDir)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("test1")
assert.NoError(t, err)
err = client.Rename("test1", "test")
assert.NoError(t, err)
// rename a missing file
err = client.Rename("test1", "test2")
assert.Error(t, err)
_, err = client.Lstat("/test1")
assert.Error(t, err, "stat for renamed dir must not succeed")
err = client.PosixRename("test", "test1")
assert.NoError(t, err)
err = client.Remove("test1")
assert.NoError(t, err)
err = client.Mkdir("/test/test1")
assert.Error(t, err, "recursive mkdir must fail")
err = client.Mkdir("/test")
assert.NoError(t, err)
err = client.Mkdir("/test/test1")
assert.NoError(t, err)
_, err = client.ReadDir("/this/dir/does/not/exist")
assert.Error(t, err, "reading a missing dir must fail")
err = client.RemoveDirectory("/test/test1")
assert.NoError(t, err)
err = client.RemoveDirectory("/test")
assert.NoError(t, err)
_, err = client.Lstat("/test")
assert.Error(t, err, "stat for deleted dir must not succeed")
_, err = client.Stat("/test")
assert.Error(t, err, "stat for deleted dir must not succeed")
err = client.RemoveDirectory("/test")
assert.Error(t, err, "remove missing path must fail")
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestRemove(t *testing.T) {
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("test")
assert.NoError(t, err)
err = client.Mkdir("/test/test1")
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join("/test", testFileName), testFileSize, client)
assert.NoError(t, err)
err = client.Remove("/test")
assert.Error(t, err, "remove non empty dir must fail")
err = client.RemoveDirectory(path.Join("/test", testFileName))
assert.Error(t, err, "remove a file with rmdir must fail")
err = client.Remove(path.Join("/test", testFileName))
assert.NoError(t, err)
err = client.Remove(path.Join("/test", testFileName))
assert.Error(t, err, "remove missing file must fail")
err = client.Remove("/test/test1")
assert.NoError(t, err)
err = client.Remove("/test")
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLink(t *testing.T) {
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Symlink(testFileName, testFileName+".link")
assert.NoError(t, err)
linkName, err := client.ReadLink(testFileName + ".link")
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testFileName), linkName)
err = client.Symlink(testFileName, testFileName+".link")
assert.Error(t, err, "creating a symlink to an existing one must fail")
err = client.Link(testFileName, testFileName+".hlink")
assert.Error(t, err, "hard link is not supported and must fail")
err = client.Remove(testFileName + ".link")
assert.NoError(t, err)
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestStat(t *testing.T) {
usePubKey := false
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
_, err := client.Lstat(testFileName)
assert.NoError(t, err)
_, err = client.Stat(testFileName)
assert.NoError(t, err)
// stat a missing path we should get an fs.ErrNotExist error
_, err = client.Stat("missing path")
assert.True(t, errors.Is(err, fs.ErrNotExist))
_, err = client.Lstat("missing path")
assert.True(t, errors.Is(err, fs.ErrNotExist))
// mode 0666 and 0444 works on Windows too
newPerm := os.FileMode(0666)
err = client.Chmod(testFileName, newPerm)
assert.NoError(t, err)
newFi, err := client.Lstat(testFileName)
assert.NoError(t, err)
assert.Equal(t, newPerm, newFi.Mode().Perm())
newPerm = os.FileMode(0444)
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.ReadLink(testFileName)
assert.Error(t, err, "readlink on a file must fail")
symlinkName := testFileName + ".sym"
err = client.Symlink(testFileName, symlinkName)
assert.NoError(t, err)
info, err := client.Lstat(symlinkName)
if assert.NoError(t, err) {
assert.True(t, info.Mode()&os.ModeSymlink != 0)
}
info, err = client.Stat(symlinkName)
if assert.NoError(t, err) {
assert.False(t, info.Mode()&os.ModeSymlink != 0)
}
linkName, err := client.ReadLink(symlinkName)
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testFileName), linkName)
newPerm = os.FileMode(0666)
err = client.Chmod(testFileName, newPerm)
assert.NoError(t, err)
err = client.Truncate(testFileName, 100)
assert.NoError(t, err)
fi, err := client.Stat(testFileName)
if assert.NoError(t, err) {
assert.Equal(t, int64(100), fi.Size())
}
f, err := client.OpenFile(testFileName, os.O_WRONLY)
if assert.NoError(t, err) {
err = f.Truncate(5)
assert.NoError(t, err)
err = f.Close()
assert.NoError(t, err)
}
f, err = client.OpenFile(testFileName, os.O_WRONLY)
newPerm = os.FileMode(0444)
if assert.NoError(t, err) {
err = f.Chmod(newPerm)
assert.NoError(t, err)
err = f.Close()
assert.NoError(t, err)
}
newFi, err = client.Lstat(testFileName)
if assert.NoError(t, err) {
assert.Equal(t, newPerm, newFi.Mode().Perm())
}
newPerm = os.FileMode(0666)
err = client.Chmod(testFileName, newPerm)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
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)
}
func TestStatChownChmod(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("chown is not supported on Windows, chmod is partially supported")
}
usePubKey := true
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Chown(testFileName, os.Getuid(), os.Getgid())
assert.NoError(t, err)
newPerm := os.FileMode(0600)
err = client.Chmod(testFileName, newPerm)
assert.NoError(t, err)
newFi, err := client.Lstat(testFileName)
assert.NoError(t, err)
assert.Equal(t, newPerm, newFi.Mode().Perm())
err = client.Remove(testFileName)
assert.NoError(t, err)
err = client.Chmod(testFileName, newPerm)
assert.EqualError(t, err, os.ErrNotExist.Error())
err = client.Chown(testFileName, os.Getuid(), os.Getgid())
assert.EqualError(t, err, os.ErrNotExist.Error())
err = os.Remove(testFilePath)
assert.NoError(t, err)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
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)
}
func TestSFTPFsLoginWrongFingerprint(t *testing.T) {
usePubKey := true
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(sftpUser, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
sftpUser.FsConfig.SFTPConfig.Fingerprints = append(sftpUser.FsConfig.SFTPConfig.Fingerprints, "wrong")
_, _, err = httpdtest.UpdateUser(sftpUser, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(sftpUser, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
out, err := runSSHCommand("md5sum", sftpUser, usePubKey)
assert.NoError(t, err)
assert.Contains(t, string(out), "d41d8cd98f00b204e9800998ecf8427e")
sftpUser.FsConfig.SFTPConfig.Fingerprints = []string{"wrong"}
_, _, err = httpdtest.UpdateUser(sftpUser, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(sftpUser, usePubKey)
if !assert.Error(t, err) {
defer conn.Close()
defer client.Close()
}
_, 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)
}
func TestChtimes(t *testing.T) {
usePubKey := false
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
testDir := "test" //nolint:goconst
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
acmodTime := time.Now()
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))
err = client.Chtimes("invalidFile", acmodTime, acmodTime)
assert.EqualError(t, err, os.ErrNotExist.Error())
err = client.Mkdir(testDir)
assert.NoError(t, err)
err = client.Chtimes(testDir, acmodTime, acmodTime)
assert.NoError(t, err)
newFi, err = client.Lstat(testDir)
assert.NoError(t, err)
diff = math.Abs(newFi.ModTime().Sub(acmodTime).Seconds())
assert.LessOrEqual(t, diff, float64(1))
err = os.Remove(testFilePath)
assert.NoError(t, err)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
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)
}
// basic tests to verify virtual chroot, should be improved to cover more cases ...
func TestEscapeHomeDir(t *testing.T) {
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
dirOutsideHome := filepath.Join(homeBasePath, defaultUsername+"1", "dir")
err = os.MkdirAll(dirOutsideHome, os.ModePerm)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
testDir := "testDir" //nolint:goconst
linkPath := filepath.Join(homeBasePath, defaultUsername, testDir)
err = os.Symlink(homeBasePath, linkPath)
assert.NoError(t, err)
_, err = client.ReadDir(testDir)
assert.Error(t, err, "reading a symbolic link outside home dir should not succeeded")
err = os.Remove(linkPath)
assert.NoError(t, err)
err = os.Symlink(dirOutsideHome, linkPath)
assert.NoError(t, err)
_, err := client.ReadDir(testDir)
assert.Error(t, err, "reading a symbolic link outside home dir should not succeeded")
err = client.Chmod(path.Join(testDir, "sub", "dir"), os.ModePerm)
assert.ErrorIs(t, err, os.ErrPermission)
assert.Error(t, err, "setstat on a file outside home dir must fail")
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteDestPath := path.Join("..", "..", testFileName)
err = sftpUploadFile(testFilePath, remoteDestPath, testFileSize, client)
assert.NoError(t, err)
_, err = client.Lstat(testFileName)
assert.NoError(t, err)
err = client.Remove(testFileName)
assert.NoError(t, err)
linkPath = filepath.Join(homeBasePath, defaultUsername, testFileName)
err = os.Symlink(homeBasePath, linkPath)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, testFilePath, 0, client)
assert.Error(t, err, "download file outside home dir must fail")
err = sftpUploadFile(testFilePath, remoteDestPath, testFileSize, client)
assert.Error(t, err, "overwrite a file outside home dir must fail")
err = client.Chmod(remoteDestPath, 0644)
assert.Error(t, err, "setstat on a file outside home dir must fail")
err = os.Remove(linkPath)
assert.NoError(t, err)
err = os.Remove(testFilePath)
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.RemoveAll(filepath.Join(homeBasePath, defaultUsername+"1"))
assert.NoError(t, err)
}
func TestEscapeSFTPFsPrefix(t *testing.T) {
usePubKey := false
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
u := getTestSFTPUser(usePubKey)
sftpPrefix := "/prefix"
outPrefix1 := "/pre"
outPrefix2 := sftpPrefix + "1"
out1 := "out1"
out2 := "out2"
u.FsConfig.SFTPConfig.Prefix = sftpPrefix
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(localUser, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir(sftpPrefix)
assert.NoError(t, err)
err = client.Mkdir(outPrefix1)
assert.NoError(t, err)
err = client.Mkdir(outPrefix2)
assert.NoError(t, err)
err = client.Symlink(outPrefix1, path.Join(sftpPrefix, out1))
assert.NoError(t, err)
err = client.Symlink(outPrefix2, path.Join(sftpPrefix, out2))
assert.NoError(t, err)
}
conn, client, err = getSftpClient(sftpUser, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
contents, err := client.ReadDir("/")
assert.NoError(t, err)
assert.Len(t, contents, 2)
_, err = client.ReadDir(out1)
assert.Error(t, err)
_, err = client.ReadDir(out2)
assert.Error(t, err)
err = client.Mkdir(path.Join(out1, "subout1"))
assert.Error(t, err)
err = client.Mkdir(path.Join(out2, "subout2"))
assert.Error(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)
}
func TestGetMimeTypeSFTPFs(t *testing.T) {
usePubKey := false
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(localUser, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
sftpFile, err := client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("some UTF-8 text so we should get a text/plain mime type")
n, err := sftpFile.Write(testData)
assert.NoError(t, err)
assert.Equal(t, len(testData), n)
err = sftpFile.Close()
assert.NoError(t, err)
}
}
sftpUser.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
sftpUser.FsConfig.SFTPConfig.PrivateKey = kms.NewEmptySecret()
fs, err := sftpUser.GetFilesystem("connID")
if assert.NoError(t, err) {
assert.True(t, vfs.IsSFTPFs(fs))
mime, err := fs.GetMimeType(testFileName)
assert.NoError(t, err)
assert.Equal(t, "text/plain; charset=utf-8", mime)
}
_, 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)
}
func TestHomeSpecialChars(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.HomeDir = filepath.Join(homeBasePath, "abc açà#&%lk")
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
files, err := client.ReadDir(".")
assert.NoError(t, err)
assert.Equal(t, 1, len(files))
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLogin(t *testing.T) {
u := getTestUser(false)
u.PublicKeys = []string{testPubKey}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, false)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.LastLogin, int64(0), "last login must be updated after a successful login: %v", user.LastLogin)
}
conn, client, err = getSftpClient(user, true)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user.Password = "invalid password"
conn, client, err = getSftpClient(user, false)
if !assert.Error(t, err, "login with invalid password must fail") {
client.Close()
conn.Close()
}
// testPubKey1 is not authorized
user.PublicKeys = []string{testPubKey1}
user.Password = ""
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, true)
if !assert.Error(t, err, "login with invalid public key must fail") {
defer conn.Close()
defer client.Close()
}
// login a user with multiple public keys, only the second one is valid
user.PublicKeys = []string{testPubKey1, testPubKey}
user.Password = ""
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, true)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginUserCert(t *testing.T) {
u := getTestUser(true)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
// try login using a cert signed from a trusted CA
signer, err := getSignerForUserCert([]byte(testCertValid))
assert.NoError(t, err)
conn, client, err := getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
// revoke the certificate
certs := []string{"SHA256:OkxVB1ImSJ2XeI8nA2Wg+6zJVlxdevD1FYBSEJjFEN4"}
data, err := json.Marshal(certs)
assert.NoError(t, err)
err = os.WriteFile(revokeUserCerts, data, 0644)
assert.NoError(t, err)
err = sftpd.Reload()
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// if we remove the revoked certificate login should work again
certs = []string{"SHA256:bsBRHC/xgiqBJdSuvSTNpJNLTISP/G356jNMCRYC5Es, SHA256:1kxVB1ImSJ2XeI8nA2Wg+6zJVlxdevD1FYBSEJjFEN4"}
data, err = json.Marshal(certs)
assert.NoError(t, err)
err = os.WriteFile(revokeUserCerts, data, 0644)
assert.NoError(t, err)
err = sftpd.Reload()
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
// try login using a cert signed from an untrusted CA
signer, err = getSignerForUserCert([]byte(testCertUntrustedCA))
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// try login using an host certificate instead of an user certificate
signer, err = getSignerForUserCert([]byte(testHostCert))
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// try login using a user certificate with an authorized source address different from localhost
signer, err = getSignerForUserCert([]byte(testCertOtherSourceAddress))
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// try login using an expired certificate
signer, err = getSignerForUserCert([]byte(testCertExpired))
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// try login using a certificate with no principals
signer, err = getSignerForUserCert([]byte(testCertNoPrincipals))
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
// the user does not exist
signer, err = getSignerForUserCert([]byte(testCertValid))
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// now login with a username not in the set of valid principals for the given certificate
u.Username += "1"
user, _, err = httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
signer, err = getSignerForUserCert([]byte(testCertValid))
assert.NoError(t, err)
conn, client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
err = os.WriteFile(revokeUserCerts, []byte(`[]`), 0644)
assert.NoError(t, err)
err = sftpd.Reload()
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestMultiStepLoginKeyAndPwd(t *testing.T) {
u := getTestUser(true)
u.Password = defaultPassword
u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, []string{
dataprovider.SSHLoginMethodKeyAndKeyboardInt,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive,
}...)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, true)
if !assert.Error(t, err, "login with public key is disallowed and must fail") {
client.Close()
conn.Close()
}
conn, client, err = getSftpClient(user, true)
if !assert.Error(t, err, "login with password is disallowed and must fail") {
client.Close()
conn.Close()
}
signer, _ := ssh.ParsePrivateKey([]byte(testPrivateKey))
authMethods := []ssh.AuthMethod{
ssh.PublicKeys(signer),
ssh.Password(defaultPassword),
}
conn, client, err = getCustomAuthSftpClient(user, authMethods, "")
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
conn, client, err = getCustomAuthSftpClient(user, authMethods, sftpSrvAddr2222)
if !assert.Error(t, err, "password auth is disabled on port 2222, multi-step auth must fail") {
client.Close()
conn.Close()
}
authMethods = []ssh.AuthMethod{
ssh.Password(defaultPassword),
ssh.PublicKeys(signer),
}
_, _, err = getCustomAuthSftpClient(user, authMethods, "")
assert.Error(t, err, "multi step auth login with wrong order must fail")
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestMultiStepLoginKeyAndKeyInt(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
u := getTestUser(true)
u.Password = defaultPassword
u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, []string{
dataprovider.SSHLoginMethodKeyAndPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive,
}...)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, 1), os.ModePerm)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, true)
if !assert.Error(t, err, "login with public key is disallowed and must fail") {
client.Close()
conn.Close()
}
signer, _ := ssh.ParsePrivateKey([]byte(testPrivateKey))
authMethods := []ssh.AuthMethod{
ssh.PublicKeys(signer),
ssh.KeyboardInteractive(func(_, _ string, _ []string, _ []bool) ([]string, error) {
return []string{"1", "2"}, nil
}),
}
conn, client, err = getCustomAuthSftpClient(user, authMethods, "")
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
conn, client, err = getCustomAuthSftpClient(user, authMethods, sftpSrvAddr2222)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
authMethods = []ssh.AuthMethod{
ssh.KeyboardInteractive(func(_, _ string, _ []string, _ []bool) ([]string, error) {
return []string{"1", "2"}, nil
}),
ssh.PublicKeys(signer),
}
_, _, err = getCustomAuthSftpClient(user, authMethods, "")
assert.Error(t, err, "multi step auth login with wrong order must fail")
authMethods = []ssh.AuthMethod{
ssh.PublicKeys(signer),
ssh.Password(defaultPassword),
}
_, _, err = getCustomAuthSftpClient(user, authMethods, "")
assert.Error(t, err, "multi step auth login with wrong method must fail")
user.Filters.DeniedLoginMethods = nil
user.Filters.DeniedLoginMethods = append(user.Filters.DeniedLoginMethods, []string{
dataprovider.SSHLoginMethodKeyAndKeyboardInt,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive,
}...)
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
_, _, err = getCustomAuthSftpClient(user, authMethods, sftpSrvAddr2222)
assert.Error(t, err)
conn, client, err = getCustomAuthSftpClient(user, authMethods, "")
if assert.NoError(t, err) {
assert.NoError(t, checkBasicSFTP(client))
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestMultiStepLoginCertAndPwd(t *testing.T) {
u := getTestUser(true)
u.Password = defaultPassword
u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, []string{
dataprovider.SSHLoginMethodKeyAndKeyboardInt,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive,
}...)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
signer, err := getSignerForUserCert([]byte(testCertValid))
assert.NoError(t, err)
authMethods := []ssh.AuthMethod{
ssh.PublicKeys(signer),
ssh.Password(defaultPassword),
}
conn, client, err := getCustomAuthSftpClient(user, authMethods, "")
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
signer, err = getSignerForUserCert([]byte(testCertOtherSourceAddress))
assert.NoError(t, err)
authMethods = []ssh.AuthMethod{
ssh.PublicKeys(signer),
ssh.Password(defaultPassword),
}
conn, client, err = getCustomAuthSftpClient(user, authMethods, "")
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginUserStatus(t *testing.T) {
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.LastLogin, int64(0), "last login must be updated after a successful login: %v", user.LastLogin)
}
user.Status = 0
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if !assert.Error(t, err, "login for a disabled user must fail") {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginUserExpiration(t *testing.T) {
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.LastLogin, int64(0), "last login must be updated after a successful login: %v", user.LastLogin)
}
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now()) - 120000
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if !assert.Error(t, err, "login for an expired user must fail") {
client.Close()
conn.Close()
}
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now()) + 120000
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginWithDatabaseCredentials(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.FsConfig.Provider = sdk.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "testbucket"
u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(`{ "type": "service_account" }`)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.GCSConfig.Credentials.GetStatus())
assert.NotEmpty(t, user.FsConfig.GCSConfig.Credentials.GetPayload())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetAdditionalData())
assert.Empty(t, user.FsConfig.GCSConfig.Credentials.GetKey())
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginInvalidFs(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.FsConfig.Provider = sdk.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if !assert.Error(t, err, "login must fail, the user has an invalid filesystem config") {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestDeniedProtocols(t *testing.T) {
u := getTestUser(true)
u.Filters.DeniedProtocols = []string{common.ProtocolSSH}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, true)
if !assert.Error(t, err, "SSH protocol is disabled, authentication must fail") {
client.Close()
conn.Close()
}
user.Filters.DeniedProtocols = []string{common.ProtocolFTP, common.ProtocolWebDAV}
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, true)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestDeniedLoginMethods(t *testing.T) {
u := getTestUser(true)
u.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.LoginMethodPassword}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, true)
if !assert.Error(t, err, "public key login is disabled, authentication must fail") {
client.Close()
conn.Close()
}
user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.LoginMethodPassword}
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, true)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user.Password = defaultPassword
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, false)
if !assert.Error(t, err, "password login is disabled, authentication must fail") {
client.Close()
conn.Close()
}
user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.SSHLoginMethodPublicKey}
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, false)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginWithIPFilters(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Filters.DeniedIP = []string{"192.167.0.0/24", "172.18.0.0/16"}
u.Filters.AllowedIP = []string{}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.LastLogin, int64(0), "last login must be updated after a successful login: %v", user.LastLogin)
}
user.Filters.AllowedIP = []string{"127.0.0.0/8"}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user.Filters.AllowedIP = []string{"172.19.0.0/16"}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if !assert.Error(t, err, "login from an not allowed IP must fail") {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginEmptyPassword(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Password = ""
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
user.Password = "empty"
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginAnonymousUser(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Password = ""
u.Filters.IsAnonymous = true
_, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.Error(t, err)
user, _, err := httpdtest.GetUserByUsername(u.Username, http.StatusOK)
assert.NoError(t, err)
assert.True(t, user.Filters.IsAnonymous)
assert.Equal(t, []string{dataprovider.PermListItems, dataprovider.PermDownload}, user.Permissions["/"])
assert.Equal(t, []string{common.ProtocolSSH, common.ProtocolHTTP}, user.Filters.DeniedProtocols)
assert.Equal(t, []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.SSHLoginMethodKeyAndPassword,
dataprovider.SSHLoginMethodKeyAndKeyboardInt, dataprovider.LoginMethodTLSCertificate,
dataprovider.LoginMethodTLSCertificateAndPwd}, user.Filters.DeniedLoginMethods)
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestAnonymousGroupInheritance(t *testing.T) {
g := getTestGroup()
g.UserSettings.Filters.IsAnonymous = true
group, _, err := httpdtest.AddGroup(g, http.StatusCreated)
assert.NoError(t, err)
usePubKey := false
u := getTestUser(usePubKey)
u.Groups = []sdk.GroupMapping{
{
Name: group.Name,
Type: sdk.GroupTypePrimary,
},
}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveGroup(group, http.StatusOK)
assert.NoError(t, err)
}
func TestLoginAfterUserUpdateEmptyPwd(t *testing.T) {
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
user.Password = ""
// password should remain unchanged
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestLoginKeyboardInteractiveAuth(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
user, _, err := httpdtest.AddUser(getTestUser(false), http.StatusCreated)
assert.NoError(t, err)
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, 1), os.ModePerm)
assert.NoError(t, err)
conn, client, err := getKeyboardInteractiveSftpClient(user, []string{"1", "2"})
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user.Status = 0
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"})
if !assert.Error(t, err, "keyboard interactive auth must fail the user is disabled") {
client.Close()
conn.Close()
}
user.Status = 1
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, -1), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"})
if !assert.Error(t, err, "keyboard interactive auth must fail the script returned -1") {
client.Close()
conn.Close()
}
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, true, 1), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"})
if !assert.Error(t, err, "keyboard interactive auth must fail the script returned bad json") {
client.Close()
conn.Close()
}
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 5, true, 1), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"})
if !assert.Error(t, err, "keyboard interactive auth must fail the script returned bad json") {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestInteractiveLoginWithPasscode(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
user, _, err := httpdtest.AddUser(getTestUser(false), http.StatusCreated)
assert.NoError(t, err)
// test password check
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptForBuiltinChecks(false, 1), os.ModePerm)
assert.NoError(t, err)
conn, client, err := getKeyboardInteractiveSftpClient(user, []string{defaultPassword})
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
// wrong password
_, _, err = getKeyboardInteractiveSftpClient(user, []string{"wrong_password"})
assert.Error(t, err)
// correct password but the script returns an error
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptForBuiltinChecks(false, 0), os.ModePerm)
assert.NoError(t, err)
_, _, err = getKeyboardInteractiveSftpClient(user, []string{"wrong_password"})
assert.Error(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 := totp.GenerateCodeCustom(key.Secret(), time.Now(), totp.ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
assert.NoError(t, err)
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptForBuiltinChecks(true, 1), os.ModePerm)
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))
}
assert.True(t, passwordAsked)
assert.True(t, passcodeAsked)
// the same passcode cannot be reused
_, _, err = getCustomAuthSftpClient(user, authMethods, "")
assert.Error(t, err)
// correct passcode but the script returns an error
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 = totp.GenerateCodeCustom(key.Secret(), time.Now(), totp.ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
assert.NoError(t, err)
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptForBuiltinChecks(true, 0), os.ModePerm)
assert.NoError(t, err)
passwordAsked = false
passcodeAsked = false
_, _, err = getCustomAuthSftpClient(user, authMethods, "")
assert.Error(t, err)
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
}),
}
_, _, err = getCustomAuthSftpClient(user, authMethods, "")
assert.Error(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 TestMustChangePasswordRequirement(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Filters.RequirePasswordChange = true
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
// public key auth works even if the user must change password
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
// password auth does not work
_, _, err = getSftpClient(user, false)
assert.Error(t, err)
// change password
err = dataprovider.UpdateUserPassword(user.Username, defaultPassword, "", "", "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, false)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSecondFactorRequirement(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Filters.TwoFactorAuthProtocols = []string{common.ProtocolSSH}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
_, _, err = getSftpClient(user, usePubKey)
assert.Error(t, err)
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)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestNamingRules(t *testing.T) {
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
providerConf.NamingRules = 7
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
usePubKey := true
u := getTestUser(usePubKey)
u.Username = "useR@user.com "
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
assert.Equal(t, "user@user.com", user.Username)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
u.Password = defaultPassword
_, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(u, false)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, err = httpdtest.RemoveUser(u, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(u.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 TestPreLoginScript(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := true
u := getTestUser(usePubKey)
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
assert.NoError(t, err)
providerConf.PreLoginHook = preLoginPath
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
err = os.WriteFile(preLoginPath, getPreLoginScriptContent(user, true), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err, "pre-login script returned a non json response, login must fail") {
client.Close()
conn.Close()
}
// now disable the the hook
user.Filters.Hooks.PreLoginDisabled = true
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user.Filters.Hooks.PreLoginDisabled = false
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
user.Status = 0
err = os.WriteFile(preLoginPath, getPreLoginScriptContent(user, false), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err, "pre-login script returned a disabled user, login must fail") {
client.Close()
conn.Close()
}
_, 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)
err = os.Remove(preLoginPath)
assert.NoError(t, err)
}
func TestPreLoginUserCreation(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
assert.NoError(t, err)
providerConf.PreLoginHook = preLoginPath
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
_, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusNotFound)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err := httpdtest.GetUserByUsername(defaultUsername, 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 = 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)
err = os.Remove(preLoginPath)
assert.NoError(t, err)
}
func TestPreLoginHookPreserveMFAConfig(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(preLoginPath, getPreLoginScriptContent(u, false), os.ModePerm)
assert.NoError(t, err)
providerConf.PreLoginHook = preLoginPath
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
// add multi-factor authentication
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, user.Filters.RecoveryCodes, 0)
assert.False(t, user.Filters.TOTPConfig.Enabled)
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},
}
for i := 0; i < 12; i++ {
user.Filters.RecoveryCodes = append(user.Filters.RecoveryCodes, dataprovider.RecoveryCode{
Secret: kms.NewPlainSecret(fmt.Sprintf("RC-%v", strings.ToUpper(util.GenerateUniqueID()))),
})
}
err = dataprovider.UpdateUser(&user, "", "", "")
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, user.Filters.RecoveryCodes, 12)
assert.True(t, user.Filters.TOTPConfig.Enabled)
assert.Equal(t, configName, user.Filters.TOTPConfig.ConfigName)
assert.Equal(t, []string{common.ProtocolSSH}, user.Filters.TOTPConfig.Protocols)
assert.Equal(t, sdkkms.SecretStatusSecretBox, user.Filters.TOTPConfig.Secret.GetStatus())
err = os.WriteFile(extAuthPath, getExitCodeScriptContent(0), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, user.Filters.RecoveryCodes, 12)
assert.True(t, user.Filters.TOTPConfig.Enabled)
assert.Equal(t, configName, user.Filters.TOTPConfig.ConfigName)
assert.Equal(t, []string{common.ProtocolSSH}, user.Filters.TOTPConfig.Protocols)
assert.Equal(t, sdkkms.SecretStatusSecretBox, user.Filters.TOTPConfig.Secret.GetStatus())
_, 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)
err = os.Remove(preLoginPath)
assert.NoError(t, err)
}
func TestPreDownloadHook(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
oldExecuteOn := common.Config.Actions.ExecuteOn
oldHook := common.Config.Actions.Hook
common.Config.Actions.ExecuteOn = []string{common.OperationPreDownload}
common.Config.Actions.Hook = preDownloadPath
usePubKey := true
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
err = os.WriteFile(preDownloadPath, getExitCodeScriptContent(0), os.ModePerm)
assert.NoError(t, err)
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
}
remoteSCPDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
err = scpDownload(localDownloadPath, remoteSCPDownPath, false, false)
assert.NoError(t, err)
err = os.WriteFile(preDownloadPath, getExitCodeScriptContent(1), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Remove(testFileName)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.ErrorIs(t, err, os.ErrPermission)
}
err = scpDownload(localDownloadPath, remoteSCPDownPath, false, false)
assert.Error(t, err)
common.Config.Actions.Hook = "http://127.0.0.1:8080/web/admin/login"
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Remove(testFileName)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
}
err = scpDownload(localDownloadPath, remoteSCPDownPath, false, false)
assert.NoError(t, err)
common.Config.Actions.Hook = "http://127.0.0.1:8080/"
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Remove(testFileName)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.ErrorIs(t, err, os.ErrPermission)
}
err = scpDownload(localDownloadPath, remoteSCPDownPath, false, false)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
common.Config.Actions.ExecuteOn = oldExecuteOn
common.Config.Actions.Hook = oldHook
}
func TestPreUploadHook(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
oldExecuteOn := common.Config.Actions.ExecuteOn
oldHook := common.Config.Actions.Hook
common.Config.Actions.ExecuteOn = []string{common.OperationPreUpload}
common.Config.Actions.Hook = preUploadPath
usePubKey := true
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
err = os.WriteFile(preUploadPath, getExitCodeScriptContent(0), os.ModePerm)
assert.NoError(t, err)
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
}
remoteSCPUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
err = scpUpload(testFilePath, remoteSCPUpPath, true, false)
assert.NoError(t, err)
err = os.WriteFile(preUploadPath, getExitCodeScriptContent(1), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.ErrorIs(t, err, os.ErrPermission)
err = sftpUploadFile(testFilePath, testFileName+"1", testFileSize, client)
assert.ErrorIs(t, err, os.ErrPermission)
}
err = scpUpload(testFilePath, remoteSCPUpPath, true, false)
assert.Error(t, err)
common.Config.Actions.Hook = "http://127.0.0.1:8080/web/client/login"
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
}
err = scpUpload(testFilePath, remoteSCPUpPath, true, false)
assert.NoError(t, err)
common.Config.Actions.Hook = "http://127.0.0.1:8080/web"
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.ErrorIs(t, err, os.ErrPermission)
err = sftpUploadFile(testFilePath, testFileName+"1", testFileSize, client)
assert.ErrorIs(t, err, os.ErrPermission)
}
err = scpUpload(testFilePath, remoteSCPUpPath, true, false)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
common.Config.Actions.ExecuteOn = oldExecuteOn
common.Config.Actions.Hook = oldHook
}
func TestPostConnectHook(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
common.Config.PostConnectHook = postConnectPath
usePubKey := true
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
err = os.WriteFile(postConnectPath, getExitCodeScriptContent(0), os.ModePerm)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
err = os.WriteFile(postConnectPath, getExitCodeScriptContent(1), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
common.Config.PostConnectHook = "http://127.0.0.1:8080/healthz"
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
common.Config.PostConnectHook = "http://127.0.0.1:8080/notfound"
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
common.Config.PostConnectHook = ""
}
func TestCheckPwdHook(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(checkPwdPath, getCheckPwdScriptsContents(2, defaultPassword), os.ModePerm)
assert.NoError(t, err)
providerConf.CheckPasswordHook = checkPwdPath
providerConf.CheckPasswordScope = 1
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
err = checkBasicSFTP(client)
assert.NoError(t, err)
client.Close()
conn.Close()
}
err = os.WriteFile(checkPwdPath, getCheckPwdScriptsContents(0, defaultPassword), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// now disable the the hook
user.Filters.Hooks.CheckPasswordDisabled = true
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
err = checkBasicSFTP(client)
assert.NoError(t, err)
client.Close()
conn.Close()
}
// enable the hook again
user.Filters.Hooks.CheckPasswordDisabled = false
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
err = os.WriteFile(checkPwdPath, getCheckPwdScriptsContents(1, ""), os.ModePerm)
assert.NoError(t, err)
user.Password = defaultPassword + "1"
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
err = checkBasicSFTP(client)
assert.NoError(t, err)
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = dataprovider.Close()
assert.NoError(t, err)
providerConf.CheckPasswordScope = 6
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
user, _, err = httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
user.Password = defaultPassword + "1"
conn, client, err = getSftpClient(user, usePubKey)
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
_, 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)
err = os.Remove(checkPwdPath)
assert.NoError(t, err)
}
func TestLoginExternalAuthPwdAndPubKey(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := true
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
testFileSize := int64(65535)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
u.Username = defaultUsername + "1"
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err, "external auth login with invalid user must fail") {
client.Close()
conn.Close()
}
usePubKey = false
u = getTestUser(usePubKey)
u.PublicKeys = []string{}
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 0, len(user.PublicKeys))
assert.Equal(t, testFileSize, user.UsedQuotaSize)
assert.Equal(t, 1, user.UsedQuotaFiles)
u.Status = 0
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err) {
client.Close()
conn.Close()
}
// now disable the the hook
user.Filters.Hooks.ExternalAuthDisabled = true
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
_, 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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestExternalAuthMultiStepLoginKeyAndPwd(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
u := getTestUser(true)
u.Password = defaultPassword
u.Filters.DeniedLoginMethods = append(u.Filters.DeniedLoginMethods, []string{
dataprovider.SSHLoginMethodKeyAndKeyboardInt,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive,
}...)
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
signer, err := ssh.ParsePrivateKey([]byte(testPrivateKey))
assert.NoError(t, err)
authMethods := []ssh.AuthMethod{
ssh.PublicKeys(signer),
ssh.Password(defaultPassword),
}
conn, client, err := getCustomAuthSftpClient(u, authMethods, "")
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
// wrong sequence should fail
authMethods = []ssh.AuthMethod{
ssh.Password(defaultPassword),
ssh.PublicKeys(signer),
}
_, _, err = getCustomAuthSftpClient(u, authMethods, "")
assert.Error(t, err)
// public key only auth must fail
_, _, err = getSftpClient(u, true)
assert.Error(t, err)
// password only auth must fail
_, _, err = getSftpClient(u, false)
assert.Error(t, err)
_, err = httpdtest.RemoveUser(u, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(u.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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestExternalAuthEmptyResponse(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
testFileSize := int64(65535)
// the user will be created
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 0, len(user.PublicKeys))
assert.Equal(t, testFileSize, user.UsedQuotaSize)
assert.Equal(t, 1, user.UsedQuotaFiles)
// now modify the user
user.MaxSessions = 10
user.QuotaFiles = 100
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, true, ""), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 10, user.MaxSessions)
assert.Equal(t, 100, user.QuotaFiles)
// the auth script accepts any password and returns an empty response, the
// user password must be updated
u.Password = defaultUsername
conn, client, err = getSftpClient(u, usePubKey)
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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestExternalAuthDifferentUsername(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
extAuthUsername := "common_user"
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, extAuthUsername), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
// the user logins using "defaultUsername" and the external auth returns "extAuthUsername"
testFileSize := int64(65535)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
// logins again to test that used quota is preserved
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = checkBasicSFTP(client)
assert.NoError(t, err)
}
_, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusNotFound)
assert.NoError(t, err)
user, _, err := httpdtest.GetUserByUsername(extAuthUsername, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 0, len(user.PublicKeys))
assert.Equal(t, testFileSize, user.UsedQuotaSize)
assert.Equal(t, 1, user.UsedQuotaFiles)
_, 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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestLoginExternalAuth(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
mappedPath := filepath.Join(os.TempDir(), "vdir1")
folderName := filepath.Base(mappedPath)
extAuthScopes := []int{1, 2}
for _, authScope := range extAuthScopes {
var usePubKey bool
if authScope == 1 {
usePubKey = false
} else {
usePubKey = true
}
u := getTestUser(usePubKey)
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: "/vpath",
QuotaFiles: 1 + authScope,
QuotaSize: 10 + int64(authScope),
})
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = authScope
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
f := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}
_, _, err = httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
if !usePubKey {
dbUser, err := dataprovider.UserExists(defaultUsername, "")
assert.NoError(t, err)
found, match := dataprovider.CheckCachedUserPassword(defaultUsername, defaultPassword, dbUser.Password)
assert.True(t, found)
assert.True(t, match)
}
u.Username = defaultUsername + "1"
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err, "external auth login with invalid user must fail") {
client.Close()
conn.Close()
}
usePubKey = !usePubKey
u = getTestUser(usePubKey)
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err, "external auth login with valid user but invalid auth scope must fail") {
client.Close()
conn.Close()
}
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
if assert.Len(t, user.VirtualFolders, 1) {
folder := user.VirtualFolders[0]
assert.Equal(t, folderName, folder.Name)
assert.Equal(t, mappedPath, folder.MappedPath)
assert.Equal(t, 1+authScope, folder.QuotaFiles)
assert.Equal(t, 10+int64(authScope), folder.QuotaSize)
}
_, 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 = 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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
}
func TestLoginExternalAuthCache(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
u := getTestUser(false)
u.Filters.ExternalAuthCacheTime = 120
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 1
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, false)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
lastLogin := user.LastLogin
assert.Greater(t, lastLogin, int64(0))
assert.Equal(t, u.Filters.ExternalAuthCacheTime, user.Filters.ExternalAuthCacheTime)
// the auth should be now cached so update the hook to return an error
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, true, false, ""), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, false)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, lastLogin, user.LastLogin)
_, 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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestLoginExternalAuthInteractive(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 4
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
err = os.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, 1), os.ModePerm)
assert.NoError(t, err)
conn, client, err := getKeyboardInteractiveSftpClient(u, []string{"1", "2"})
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
u.Username = defaultUsername + "1"
conn, client, err = getKeyboardInteractiveSftpClient(u, []string{"1", "2"})
if !assert.Error(t, err, "external auth login with invalid user must fail") {
client.Close()
conn.Close()
}
usePubKey = true
u = getTestUser(usePubKey)
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err, "external auth login with valid user but invalid auth scope must fail") {
client.Close()
conn.Close()
}
user, _, err := httpdtest.GetUserByUsername(defaultUsername, 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 = 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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestLoginExternalAuthErrors(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := true
u := getTestUser(usePubKey)
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, true, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, usePubKey)
if !assert.Error(t, err, "login must fail, external auth returns a non json response") {
client.Close()
conn.Close()
}
usePubKey = false
u = getTestUser(usePubKey)
conn, client, err = getSftpClient(u, usePubKey)
if !assert.Error(t, err, "login must fail, external auth returns a non json response") {
client.Close()
conn.Close()
}
_, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusNotFound)
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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestExternalAuthReturningAnonymousUser(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
u.Filters.IsAnonymous = true
u.Password = ""
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
_, _, err = getSftpClient(u, usePubKey)
assert.Error(t, err)
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.True(t, user.Filters.IsAnonymous)
assert.Equal(t, []string{dataprovider.PermListItems, dataprovider.PermDownload}, user.Permissions["/"])
assert.Equal(t, []string{common.ProtocolSSH, common.ProtocolHTTP}, user.Filters.DeniedProtocols)
assert.Equal(t, []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.SSHLoginMethodKeyAndPassword,
dataprovider.SSHLoginMethodKeyAndKeyboardInt, dataprovider.LoginMethodTLSCertificate,
dataprovider.LoginMethodTLSCertificateAndPwd}, user.Filters.DeniedLoginMethods)
// test again, the user now exists
_, _, err = getSftpClient(u, usePubKey)
assert.Error(t, err)
updatedUser, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
user.UpdatedAt = updatedUser.UpdatedAt
user.LastPasswordChange = updatedUser.LastPasswordChange
assert.Equal(t, user, updatedUser)
_, 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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestExternalAuthPreserveMFAConfig(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
err := dataprovider.Close()
assert.NoError(t, err)
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, false, ""), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
err = dataprovider.Initialize(providerConf, configDir, true)
assert.NoError(t, err)
conn, client, err := getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
// add multi-factor authentication
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, user.Filters.RecoveryCodes, 0)
assert.False(t, user.Filters.TOTPConfig.Enabled)
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},
}
for i := 0; i < 12; i++ {
user.Filters.RecoveryCodes = append(user.Filters.RecoveryCodes, dataprovider.RecoveryCode{
Secret: kms.NewPlainSecret(fmt.Sprintf("RC-%v", strings.ToUpper(util.GenerateUniqueID()))),
})
}
err = dataprovider.UpdateUser(&user, "", "", "")
assert.NoError(t, err)
// login again and check that the MFA configs are preserved
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, user.Filters.RecoveryCodes, 12)
assert.True(t, user.Filters.TOTPConfig.Enabled)
assert.Equal(t, configName, user.Filters.TOTPConfig.ConfigName)
assert.Equal(t, []string{common.ProtocolSSH}, user.Filters.TOTPConfig.Protocols)
assert.Equal(t, sdkkms.SecretStatusSecretBox, user.Filters.TOTPConfig.Secret.GetStatus())
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, true, ""), os.ModePerm)
assert.NoError(t, err)
conn, client, err = getSftpClient(u, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, user.Filters.RecoveryCodes, 12)
assert.True(t, user.Filters.TOTPConfig.Enabled)
assert.Equal(t, configName, user.Filters.TOTPConfig.ConfigName)
assert.Equal(t, []string{common.ProtocolSSH}, user.Filters.TOTPConfig.Protocols)
assert.Equal(t, sdkkms.SecretStatusSecretBox, user.Filters.TOTPConfig.Secret.GetStatus())
_, 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)
err = os.Remove(extAuthPath)
assert.NoError(t, err)
}
func TestQuotaDisabledError(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)
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaFiles = 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName+"1", testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName+"1", testFileName+".rename") //nolint:goconst
assert.NoError(t, err)
err = os.Remove(testFilePath)
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)
}
//nolint:dupl
func TestMaxConnections(t *testing.T) {
oldValue := common.Config.MaxTotalConnections
common.Config.MaxTotalConnections = 1
assert.Eventually(t, func() bool {
return common.Connections.GetClientConnections() == 0
}, 1000*time.Millisecond, 50*time.Millisecond)
usePubKey := true
user := getTestUser(usePubKey)
err := dataprovider.AddUser(&user, "", "", "")
assert.NoError(t, err)
user.Password = ""
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
assert.NoError(t, checkBasicSFTP(client))
s, c, err := getSftpClient(user, usePubKey)
if !assert.Error(t, err, "max total connections exceeded, new login should not succeed") {
c.Close()
s.Close()
}
err = client.Close()
assert.NoError(t, err)
err = conn.Close()
assert.NoError(t, err)
}
err = dataprovider.DeleteUser(user.Username, "", "", "")
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
common.Config.MaxTotalConnections = oldValue
}
//nolint:dupl
func TestMaxPerHostConnections(t *testing.T) {
oldValue := common.Config.MaxPerHostConnections
common.Config.MaxPerHostConnections = 1
assert.Eventually(t, func() bool {
return common.Connections.GetClientConnections() == 0
}, 1000*time.Millisecond, 50*time.Millisecond)
usePubKey := true
user := getTestUser(usePubKey)
err := dataprovider.AddUser(&user, "", "", "")
assert.NoError(t, err)
user.Password = ""
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
assert.NoError(t, checkBasicSFTP(client))
s, c, err := getSftpClient(user, usePubKey)
if !assert.Error(t, err, "max per host connections exceeded, new login should not succeed") {
c.Close()
s.Close()
}
err = client.Close()
assert.NoError(t, err)
err = conn.Close()
assert.NoError(t, err)
}
err = dataprovider.DeleteUser(user.Username, "", "", "")
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
common.Config.MaxPerHostConnections = oldValue
}
func TestMaxSessions(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Username += "1"
u.MaxSessions = 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
s, c, err := getSftpClient(user, usePubKey)
if !assert.Error(t, err, "max sessions exceeded, new login should not succeed") {
c.Close()
s.Close()
}
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSupportedExtensions(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
v, ok := client.HasExtension("statvfs@openssh.com")
assert.Equal(t, "2", v)
assert.True(t, ok)
_, ok = client.HasExtension("hardlink@openssh.com")
assert.False(t, ok)
_, ok = client.HasExtension("posix-rename@openssh.com")
assert.False(t, ok)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestQuotaFileReplace(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.QuotaFiles = 1000
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(65535)
testFilePath := filepath.Join(homeBasePath, testFileName)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) { //nolint:dupl
defer conn.Close()
defer client.Close()
expectedQuotaSize := testFileSize
expectedQuotaFiles := 1
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
// now replace the same file, the quota must not change
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
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 create a symlink, replace it with a file and check the quota
// replacing a symlink is like uploading a new file
err = client.Symlink(testFileName, testFileName+".link")
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
expectedQuotaFiles++
expectedQuotaSize += testFileSize
err = sftpUploadFile(testFilePath, testFileName+".link", testFileSize, client)
assert.NoError(t, err)
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 set a quota size restriction and upload the same file, upload should fail for space limit exceeded
user.QuotaSize = testFileSize*2 - 1
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.Error(t, err, "quota size exceeded, file upload must fail")
err = client.Remove(testFileName)
assert.NoError(t, err)
}
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
user.ID = 0
user.CreatedAt = 0
user.QuotaSize = 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.Remove(testFilePath)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
}
func TestQuotaRename(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.QuotaFiles = 1000
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(65535)
testFileSize1 := int64(65537)
testFileName1 := "test_file1.dat" //nolint:goconst
testFilePath := filepath.Join(homeBasePath, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName, testFileName+".rename")
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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)
err = client.Rename(testFileName1, 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)
err = client.Symlink(testFileName+".rename", testFileName+".symlink") //nolint:goconst
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
// overwrite a symlink
err = client.Rename(testFileName, testFileName+".symlink")
assert.NoError(t, err)
err = client.Mkdir("testdir")
assert.NoError(t, err)
err = client.Rename("testdir", "testdir1")
assert.NoError(t, err)
err = client.Mkdir("testdir")
assert.NoError(t, err)
err = client.Rename("testdir", "testdir1")
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, testFileSize+testFileSize1, user.UsedQuotaSize)
testDir := "tdir"
err = client.Mkdir(testDir)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 4, user.UsedQuotaFiles)
assert.Equal(t, testFileSize*2+testFileSize1*2, user.UsedQuotaSize)
err = client.Rename(testDir, testDir+"1")
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 4, user.UsedQuotaFiles)
assert.Equal(t, testFileSize*2+testFileSize1*2, user.UsedQuotaSize)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
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.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
}
func TestQuotaScan(t *testing.T) {
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(65535)
expectedQuotaSize := user.UsedQuotaSize + testFileSize
expectedQuotaFiles := user.UsedQuotaFiles + 1
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
// create user with the same home dir, so there is at least an untracked file
user, _, err = httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
_, err = httpdtest.StartQuotaScan(user, http.StatusAccepted)
assert.NoError(t, err)
assert.Eventually(t, func() bool {
scans, _, err := httpdtest.GetQuotaScans(http.StatusOK)
if err == nil {
return len(scans) == 0
}
return false
}, 1*time.Second, 50*time.Millisecond)
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 = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestMultipleQuotaScans(t *testing.T) {
res := common.QuotaScans.AddUserQuotaScan(defaultUsername, "")
assert.True(t, res)
res = common.QuotaScans.AddUserQuotaScan(defaultUsername, "")
assert.False(t, res, "add quota must fail if another scan is already active")
assert.True(t, common.QuotaScans.RemoveUserQuotaScan(defaultUsername))
activeScans := common.QuotaScans.GetUsersQuotaScans("")
assert.Equal(t, 0, len(activeScans))
assert.False(t, common.QuotaScans.RemoveUserQuotaScan(defaultUsername))
}
func TestQuotaLimits(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaFiles = 1
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.QuotaFiles = 1
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(65535)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
testFileSize1 := int64(131072)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
testFileSize2 := int64(32768)
testFileName2 := "test_file2.dat" //nolint:goconst
testFilePath2 := filepath.Join(homeBasePath, testFileName2)
err = createTestFile(testFilePath2, testFileSize2)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
// test quota files
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client) //nolint:goconst
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName+".quota.1", testFileSize, client)
if assert.Error(t, err, "user is over quota files, upload must fail") {
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
}
// rename should work
err = client.Rename(testFileName+".quota", testFileName)
assert.NoError(t, err)
}
// test quota size
user.QuotaSize = testFileSize - 1
user.QuotaFiles = 0
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName+".quota.1", testFileSize, client)
if assert.Error(t, err, "user is over quota size, upload must fail") {
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
}
err = client.Rename(testFileName, testFileName+".quota")
assert.NoError(t, err)
err = client.Rename(testFileName+".quota", testFileName)
assert.NoError(t, err)
}
// now test quota limits while uploading the current file, we have 1 bytes remaining
user.QuotaSize = testFileSize + 1
user.QuotaFiles = 0
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
}
_, err = client.Stat(testFileName1)
assert.Error(t, err)
_, err = client.Lstat(testFileName1)
assert.Error(t, err)
// overwriting an existing file will work if the resulting size is lesser or equal than the current one
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath2, testFileName, testFileSize2, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, testFileName, testFileSize1, client)
assert.Error(t, err)
_, err := client.Stat(testFileName)
assert.Error(t, err)
}
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
user.ID = 0
user.CreatedAt = 0
_, resp, err := httpdtest.AddUser(user, http.StatusCreated)
assert.NoError(t, err, string(resp))
}
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
err = os.Remove(testFilePath2)
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)
}
func TestTransferQuotaLimits(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.DownloadDataTransfer = 1
u.UploadDataTransfer = 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(550000)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
// error while download is active
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
}
// error before starting the download
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), common.ErrReadQuotaExceeded.Error())
}
// error while upload is active
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
}
// error before starting the upload
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
}
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.UsedDownloadDataTransfer, int64(1024*1024))
assert.Greater(t, user.UsedUploadDataTransfer, int64(1024*1024))
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestUploadMaxSize(t *testing.T) {
testFileSize := int64(65535)
usePubKey := false
u := getTestUser(usePubKey)
u.Filters.MaxUploadFileSize = testFileSize + 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
testFileSize1 := int64(131072)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client)
assert.Error(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
// now test overwrite an existing file with a size bigger than the allowed one
err = createTestFile(filepath.Join(user.GetHomeDir(), testFileName1), testFileSize1)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client)
assert.Error(t, err)
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestBandwidthAndConnections(t *testing.T) {
usePubKey := false
testFileSize := int64(524288)
u := getTestUser(usePubKey)
u.UploadBandwidth = 120
u.DownloadBandwidth = 100
wantedUploadElapsed := 1000 * (testFileSize / 1024) / u.UploadBandwidth
wantedDownloadElapsed := 1000 * (testFileSize / 1024) / u.DownloadBandwidth
// 100 ms tolerance
wantedUploadElapsed -= 100
wantedDownloadElapsed -= 100
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
startTime := time.Now()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
elapsed := time.Since(startTime).Nanoseconds() / 1000000
assert.GreaterOrEqual(t, elapsed, wantedUploadElapsed, "upload bandwidth throttling not respected")
startTime = time.Now()
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
c := sftpDownloadNonBlocking(testFileName, localDownloadPath, testFileSize, client)
waitForActiveTransfers(t)
// wait some additional arbitrary time to wait for transfer activity to happen
// it is need to reach all the code in CheckIdleConnections
time.Sleep(100 * time.Millisecond)
err = <-c
assert.NoError(t, err)
elapsed = time.Since(startTime).Nanoseconds() / 1000000
assert.GreaterOrEqual(t, elapsed, wantedDownloadElapsed, "download bandwidth throttling not respected")
// test disconnection
c = sftpUploadNonBlocking(testFilePath, testFileName+"_partial", testFileSize, client)
waitForActiveTransfers(t)
time.Sleep(100 * time.Millisecond)
for _, stat := range common.Connections.GetStats("") {
common.Connections.Close(stat.ConnectionID, "")
}
err = <-c
assert.Error(t, err, "connection closed while uploading: the upload must fail")
assert.Eventually(t, func() bool {
return len(common.Connections.GetStats("")) == 0
}, 10*time.Second, 200*time.Millisecond)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPatternsFilters(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName+".zip", testFileSize, client)
assert.NoError(t, err)
}
user.Filters.FilePatterns = []sdk.PatternsFilter{
{
Path: "/",
AllowedPatterns: []string{"*.zIp"},
DeniedPatterns: []string{},
},
}
user.Filters.DisableFsChecks = true
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.Error(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.Error(t, err)
err = client.Rename(testFileName, testFileName+"1")
assert.Error(t, err)
err = client.Remove(testFileName)
assert.Error(t, err)
err = sftpDownloadFile(testFileName+".zip", localDownloadPath, testFileSize, client)
assert.NoError(t, err)
err = client.Mkdir("dir.zip")
assert.NoError(t, err)
err = client.Rename("dir.zip", "dir1.zip")
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestVirtualFolders(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
mappedPath := filepath.Join(os.TempDir(), "vdir")
folderName := filepath.Base(mappedPath)
vdirPath := "/vdir/subdir"
testDir := "/userDir"
testDir1 := "/userDir1"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: vdirPath,
})
u.Permissions[testDir] = []string{dataprovider.PermCreateDirs}
u.Permissions[testDir1] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermRename}
u.Permissions[path.Join(testDir1, "subdir")] = []string{dataprovider.PermRename}
f := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
// check virtual folder auto creation
_, err = os.Stat(mappedPath)
assert.NoError(t, err)
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpUploadFile(testFilePath, path.Join(vdirPath, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(path.Join(vdirPath, testFileName), localDownloadPath, testFileSize, client)
assert.NoError(t, err)
err = client.Rename(vdirPath, "new_name")
assert.Error(t, err, "renaming a virtual folder must fail")
err = client.RemoveDirectory(vdirPath)
assert.Error(t, err, "removing a virtual folder must fail")
err = client.Mkdir(vdirPath)
assert.Error(t, err, "creating a virtual folder must fail")
err = client.Symlink(path.Join(vdirPath, testFileName), vdirPath)
assert.Error(t, err, "symlink to a virtual folder must fail")
err = client.Rename("/vdir", "/vdir1")
assert.Error(t, err, "renaming a directory with a virtual folder inside must fail")
err = client.RemoveDirectory("/vdir")
assert.Error(t, err, "removing a directory with a virtual folder inside must fail")
err = client.Mkdir("vdir1")
assert.NoError(t, err)
// rename empty dir /vdir1, we have permission on /
err = client.Rename("vdir1", "vdir2")
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join("vdir2", testFileName), testFileSize, client)
assert.NoError(t, err)
// we don't have rename permission in testDir and vdir2 contains a file
err = client.Rename("vdir2", testDir)
assert.Error(t, err)
err = client.Rename("vdir2", testDir1)
assert.NoError(t, err)
err = client.Rename(testDir1, "vdir2")
assert.NoError(t, err)
err = client.MkdirAll(path.Join("vdir2", "subdir"))
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join("vdir2", "subdir", testFileName), testFileSize, client)
assert.NoError(t, err)
err = client.Rename("vdir2", testDir1)
assert.NoError(t, err)
err = client.Rename(testDir1, "vdir2")
assert.NoError(t, err)
err = client.MkdirAll(path.Join("vdir2", "subdir", "subdir"))
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join("vdir2", "subdir", "subdir", testFileName), testFileSize, client)
assert.NoError(t, err)
err = client.Rename("vdir2", testDir1)
assert.NoError(t, err)
err = client.Rename(testDir1, "vdir3")
assert.NoError(t, err)
err = client.Remove(path.Join("vdir3", "subdir", "subdir", testFileName))
assert.NoError(t, err)
err = client.RemoveDirectory(path.Join("vdir3", "subdir", "subdir"))
assert.NoError(t, err)
err = client.Rename("vdir3", testDir1)
assert.NoError(t, err)
err = client.Rename(testDir1, "vdir2")
assert.NoError(t, err)
err = client.Symlink(path.Join("vdir2", "subdir", testFileName), path.Join("vdir2", "subdir", "alink"))
assert.NoError(t, err)
err = client.Rename("vdir2", testDir1)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
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 TestVirtualFoldersQuotaLimit(t *testing.T) {
usePubKey := false
u1 := getTestUser(usePubKey)
u1.QuotaFiles = 1
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
u1.VirtualFolders = append(u1.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName1,
},
VirtualPath: vdirPath1,
QuotaFiles: -1,
QuotaSize: -1,
})
u1.VirtualFolders = append(u1.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
},
VirtualPath: vdirPath2,
QuotaFiles: 1,
QuotaSize: 0,
})
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
err := createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
u2 := getTestUser(usePubKey)
u2.QuotaSize = testFileSize + 1
u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName1,
},
VirtualPath: vdirPath1,
QuotaFiles: -1,
QuotaSize: -1,
})
u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
},
VirtualPath: vdirPath2,
QuotaFiles: 0,
QuotaSize: testFileSize + 1,
})
users := []dataprovider.User{u1, u2}
for _, u := range users {
err = os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.Error(t, err)
_, err = client.Stat(testFileName)
assert.Error(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName+"1"), testFileSize, client)
assert.Error(t, err)
_, err = client.Stat(path.Join(vdirPath1, testFileName+"1"))
assert.Error(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+"1"), testFileSize, client)
assert.Error(t, err)
_, err = client.Stat(path.Join(vdirPath2, testFileName+"1"))
assert.Error(t, err)
err = client.Remove(path.Join(vdirPath1, testFileName))
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
assert.Error(t, err)
// now test renames
err = client.Rename(testFileName, path.Join(vdirPath1, testFileName))
assert.NoError(t, err)
err = client.Rename(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testFileName+".rename"))
assert.NoError(t, err)
err = client.Rename(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testFileName+".rename"))
assert.NoError(t, err)
err = client.Rename(path.Join(vdirPath2, testFileName+".rename"), testFileName+".rename")
assert.Error(t, err)
err = client.Rename(path.Join(vdirPath2, testFileName+".rename"), path.Join(vdirPath1, testFileName))
assert.Error(t, err)
err = client.Rename(path.Join(vdirPath1, testFileName+".rename"), path.Join(vdirPath2, testFileName))
assert.Error(t, err)
err = client.Rename(path.Join(vdirPath1, testFileName+".rename"), testFileName)
assert.NoError(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 = 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.Remove(testFilePath)
assert.NoError(t, err)
}
func TestSFTPLoopSimple(t *testing.T) {
usePubKey := false
user1 := getTestSFTPUser(usePubKey)
user2 := getTestSFTPUser(usePubKey)
user1.Username += "1"
user2.Username += "2"
user1.FsConfig.Provider = sdk.SFTPFilesystemProvider
user2.FsConfig.Provider = sdk.SFTPFilesystemProvider
user1.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
Endpoint: sftpServerAddr,
Username: user2.Username,
},
Password: kms.NewPlainSecret(defaultPassword),
}
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))
_, _, err = getSftpClient(user1, usePubKey)
assert.Error(t, err)
_, _, err = getSftpClient(user2, usePubKey)
assert.Error(t, err)
user1.FsConfig.SFTPConfig.Username = user1.Username
user1.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
_, _, err = httpdtest.UpdateUser(user1, http.StatusOK, "")
assert.NoError(t, err)
_, _, err = getSftpClient(user1, usePubKey)
assert.Error(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)
}
func TestSFTPLoopVirtualFolders(t *testing.T) {
usePubKey := false
sftpFloderName := "sftp"
user1 := getTestUser(usePubKey)
user2 := getTestSFTPUser(usePubKey)
user3 := getTestSFTPUser(usePubKey)
user1.Username += "1"
user2.Username += "2"
user3.Username += "3"
// user1 is a local account with a virtual SFTP folder to user2
// user2 has user1 as SFTP fs
user1.VirtualFolders = append(user1.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: sftpFloderName,
},
VirtualPath: "/vdir",
})
user2.FsConfig.Provider = sdk.SFTPFilesystemProvider
user2.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
Endpoint: sftpServerAddr,
Username: user1.Username,
},
Password: kms.NewPlainSecret(defaultPassword),
}
user3.FsConfig.Provider = sdk.SFTPFilesystemProvider
user3.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
Endpoint: sftpServerAddr,
Username: user1.Username,
},
Password: kms.NewPlainSecret(defaultPassword),
}
f := vfs.BaseVirtualFolder{
Name: sftpFloderName,
FsConfig: vfs.Filesystem{
Provider: sdk.SFTPFilesystemProvider,
SFTPConfig: vfs.SFTPFsConfig{
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
Endpoint: sftpServerAddr,
Username: user2.Username,
EqualityCheckMode: 1,
},
Password: kms.NewPlainSecret(defaultPassword),
},
},
}
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
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))
user3, resp, err = httpdtest.AddUser(user3, http.StatusCreated)
assert.NoError(t, err, string(resp))
// login will work but /vdir will not be accessible
conn, client, err := getSftpClient(user1, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
_, err = client.ReadDir("/vdir")
assert.Error(t, err)
}
// now make user2 a local account with an SFTP virtual folder to user1.
// So we have:
// user1 -> local account with the SFTP virtual folder /vdir to user2
// user2 -> local account with the SFTP virtual folder /vdir2 to user3
// user3 -> sftp user with user1 as fs
user2.FsConfig.Provider = sdk.LocalFilesystemProvider
user2.FsConfig.SFTPConfig = vfs.SFTPFsConfig{}
user2.VirtualFolders = append(user2.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: sftpFloderName,
FsConfig: vfs.Filesystem{
Provider: sdk.SFTPFilesystemProvider,
SFTPConfig: vfs.SFTPFsConfig{
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
Endpoint: sftpServerAddr,
Username: user3.Username,
},
Password: kms.NewPlainSecret(defaultPassword),
},
},
},
VirtualPath: "/vdir2",
})
user2, _, err = httpdtest.UpdateUser(user2, http.StatusOK, "")
assert.NoError(t, err)
// login will work but /vdir will not be accessible
conn, client, err = getSftpClient(user1, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
_, err = client.ReadDir("/vdir")
assert.Error(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.RemoveUser(user3, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user3.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: sftpFloderName}, http.StatusOK)
assert.NoError(t, err)
}
func TestNestedVirtualFolders(t *testing.T) {
usePubKey := true
baseUser, resp, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err, string(resp))
u := getTestSFTPUser(usePubKey)
u.QuotaFiles = 1000
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,
QuotaFiles: 100,
})
mappedPath := filepath.Join(os.TempDir(), "local")
folderName := filepath.Base(mappedPath)
vdirPath := "/vdir/local"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: vdirPath,
QuotaFiles: -1,
QuotaSize: -1,
})
mappedPathNested := filepath.Join(os.TempDir(), "nested")
folderNameNested := filepath.Base(mappedPathNested)
vdirNestedPath := "/vdir/crypt/nested"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderNameNested,
},
VirtualPath: vdirNestedPath,
QuotaFiles: -1,
QuotaSize: -1,
})
f1 := vfs.BaseVirtualFolder{
Name: folderNameCrypt,
FsConfig: vfs.Filesystem{
Provider: sdk.CryptedFilesystemProvider,
CryptConfig: vfs.CryptFsConfig{
Passphrase: kms.NewPlainSecret(defaultPassword),
},
},
MappedPath: mappedPathCrypt,
}
_, _, err = httpdtest.AddFolder(f1, http.StatusCreated)
assert.NoError(t, err)
f2 := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
assert.NoError(t, err)
f3 := vfs.BaseVirtualFolder{
Name: folderNameNested,
MappedPath: mappedPathNested,
}
_, _, err = httpdtest.AddFolder(f3, 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, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
expectedQuotaSize := int64(0)
expectedQuotaFiles := 0
fileSize := int64(32765)
err = writeSFTPFile(testFileName, fileSize, client)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
fileSize = 38764
err = writeSFTPFile(path.Join("/vdir", testFileName), fileSize, client)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
fileSize = 18769
err = writeSFTPFile(path.Join(vdirPath, testFileName), fileSize, client)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
fileSize = 27658
err = writeSFTPFile(path.Join(vdirNestedPath, testFileName), fileSize, client)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
fileSize = 39765
err = writeSFTPFile(path.Join(vdirCryptPath, testFileName), fileSize, client)
assert.NoError(t, err)
userGet, _, err := httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, userGet.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize, userGet.UsedQuotaSize)
folderGet, _, err := httpdtest.GetFolderByName(folderNameCrypt, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, folderGet.UsedQuotaSize, fileSize)
assert.Equal(t, 1, folderGet.UsedQuotaFiles)
folderGet, _, err = httpdtest.GetFolderByName(folderName, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, int64(18769), folderGet.UsedQuotaSize)
assert.Equal(t, 1, folderGet.UsedQuotaFiles)
folderGet, _, err = httpdtest.GetFolderByName(folderNameNested, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, int64(27658), folderGet.UsedQuotaSize)
assert.Equal(t, 1, folderGet.UsedQuotaFiles)
files, err := client.ReadDir("/")
if assert.NoError(t, err) {
assert.Len(t, files, 2)
}
info, err := client.Stat("vdir")
if assert.NoError(t, err) {
assert.True(t, info.IsDir())
}
files, err = client.ReadDir("/vdir")
if assert.NoError(t, err) {
assert.Len(t, files, 3)
}
files, err = client.ReadDir(vdirCryptPath)
if assert.NoError(t, err) {
assert.Len(t, files, 2)
}
info, err = client.Stat(vdirNestedPath)
if assert.NoError(t, err) {
assert.True(t, info.IsDir())
}
// finally add some files directly using os method and then check quota
fName := "testfile"
fileSize = 123456
err = createTestFile(filepath.Join(baseUser.HomeDir, fName), fileSize)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
fileSize = 8765
err = createTestFile(filepath.Join(mappedPath, fName), fileSize)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
fileSize = 98751
err = createTestFile(filepath.Join(mappedPathNested, fName), fileSize)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
err = createTestFile(filepath.Join(mappedPathCrypt, fName), fileSize)
assert.NoError(t, err)
_, err = httpdtest.StartQuotaScan(user, http.StatusAccepted)
assert.NoError(t, err)
assert.Eventually(t, func() bool {
scans, _, err := httpdtest.GetQuotaScans(http.StatusOK)
if err == nil {
return len(scans) == 0
}
return false
}, 1*time.Second, 50*time.Millisecond)
userGet, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, userGet.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize, userGet.UsedQuotaSize)
// the crypt folder is not included within user quota so we need to do a separate scan
_, err = httpdtest.StartFolderQuotaScan(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusAccepted)
assert.NoError(t, err)
assert.Eventually(t, func() bool {
scans, _, err := httpdtest.GetFoldersQuotaScans(http.StatusOK)
if err == nil {
return len(scans) == 0
}
return false
}, 1*time.Second, 50*time.Millisecond)
folderGet, _, err = httpdtest.GetFolderByName(folderNameCrypt, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, folderGet.UsedQuotaSize, int64(39765+98751))
assert.Equal(t, 2, folderGet.UsedQuotaFiles)
}
_, 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: folderName}, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameNested}, 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(mappedPath)
assert.NoError(t, err)
err = os.RemoveAll(mappedPathNested)
assert.NoError(t, err)
}
func TestBufferedUser(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
u.FsConfig.OSConfig = sdk.OSFsConfig{
WriteBufferSize: 2,
ReadBufferSize: 1,
}
vdirPath := "/crypted"
mappedPath := filepath.Join(os.TempDir(), util.GenerateUniqueID())
folderName := filepath.Base(mappedPath)
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: vdirPath,
QuotaFiles: -1,
QuotaSize: -1,
})
f := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
FsConfig: vfs.Filesystem{
Provider: sdk.CryptedFilesystemProvider,
CryptConfig: vfs.CryptFsConfig{
OSFsConfig: sdk.OSFsConfig{
WriteBufferSize: 3,
ReadBufferSize: 2,
},
Passphrase: kms.NewPlainSecret(defaultPassword),
},
},
}
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
expectedQuotaSize := int64(0)
expectedQuotaFiles := 0
fileSize := int64(32768)
err = writeSFTPFile(testFileName, fileSize, client)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
err = writeSFTPFile(path.Join(vdirPath, testFileName), fileSize, client)
assert.NoError(t, err)
expectedQuotaSize += fileSize
expectedQuotaFiles++
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
assert.Greater(t, user.UsedQuotaSize, expectedQuotaSize)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, fileSize, client)
assert.NoError(t, err)
err = sftpDownloadFile(path.Join(vdirPath, testFileName), localDownloadPath, fileSize, client)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
err = client.Remove(testFileName)
assert.NoError(t, err)
err = client.Remove(path.Join(vdirPath, testFileName))
assert.NoError(t, err)
data := []byte("test data")
f, err := client.OpenFile(testFileName, os.O_WRONLY|os.O_CREATE)
if assert.NoError(t, err) {
n, err := f.Write(data)
assert.NoError(t, err)
assert.Equal(t, len(data), n)
err = f.Truncate(2)
assert.NoError(t, err)
expectedQuotaSize := int64(2)
expectedQuotaFiles := 0
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(data)
assert.NoError(t, err)
assert.Equal(t, len(data), 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(data)
assert.NoError(t, err)
assert.Equal(t, len(data), n)
err = f.Close()
assert.NoError(t, err)
expectedQuotaSize = int64(5) + int64(len(data))
expectedQuotaFiles = 1
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)
}
_, 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 TestTruncateQuotaLimits(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.QuotaSize = 20
mappedPath := filepath.Join(os.TempDir(), "mapped")
folderName := filepath.Base(mappedPath)
err := os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
vdirPath := "/vmapped"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: vdirPath,
QuotaFiles: 10,
})
f := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}
_, _, err = httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
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, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
data := []byte("test data")
f, err := client.OpenFile(testFileName, os.O_WRONLY|os.O_CREATE)
if assert.NoError(t, err) {
n, err := f.Write(data)
assert.NoError(t, err)
assert.Equal(t, len(data), 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(data)
assert.NoError(t, err)
assert.Equal(t, len(data), 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(data)
assert.NoError(t, err)
assert.Equal(t, len(data), n)
err = f.Close()
assert.NoError(t, err)
expectedQuotaFiles = 1
expectedQuotaSize = int64(5) + int64(len(data))
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(data)
assert.NoError(t, err)
assert.Equal(t, len(data), 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(data)
assert.NoError(t, err)
assert.Equal(t, len(data), 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(data)
assert.NoError(t, err)
assert.Equal(t, len(data), 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(data)
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
vfileName := path.Join(vdirPath, testFileName)
f, err = client.OpenFile(vfileName, os.O_WRONLY|os.O_CREATE)
if assert.NoError(t, err) {
n, err := f.Write(data)
assert.NoError(t, err)
assert.Equal(t, len(data), n)
err = f.Truncate(2)
assert.NoError(t, err)
expectedQuotaFiles := 0
expectedQuotaSize := int64(2)
fold, _, err := httpdtest.GetFolderByName(folderName, 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(folderName, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
}
err = client.Truncate(vfileName, 1)
assert.NoError(t, err)
fold, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, int64(1), fold.UsedQuotaSize)
assert.Equal(t, 1, fold.UsedQuotaFiles)
// cleanup
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
user.ID = 0
user.CreatedAt = 0
user.QuotaSize = 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(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
}
func TestVirtualFoldersQuotaRenameOverwrite(t *testing.T) {
usePubKey := true
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize1 := int64(65537)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err := createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
u := getTestUser(usePubKey)
u.QuotaFiles = 0
u.QuotaSize = 0
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
folderName1 := filepath.Base(mappedPath1)
vdirPath1 := "/vdir1"
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
folderName2 := filepath.Base(mappedPath2)
vdirPath2 := "/vdir2"
mappedPath3 := filepath.Join(os.TempDir(), "vdir3")
folderName3 := filepath.Base(mappedPath3)
vdirPath3 := "/vdir3"
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,
})
err = os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath3, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath3, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath3, testFileName+"1"), testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName, path.Join(vdirPath1, testFileName+".rename"))
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 = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, 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"
testFilePath2 := filepath.Join(homeBasePath, testFileName2)
err = createTestFile(testFilePath2, testFileSize+testFileSize1)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath2, 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)
err = os.Remove(testFilePath2)
assert.NoError(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 = sftpUploadFile(testFilePath1, path.Join(aDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
}
func TestVirtualFoldersQuotaValues(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaFiles = 100
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
vdirPath1 := "/vdir1" //nolint:goconst
folderName1 := filepath.Base(mappedPath1)
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
vdirPath2 := "/vdir2" //nolint:goconst
folderName2 := filepath.Base(mappedPath2)
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,
})
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
// we copy the same file two times to test quota update on file overwrite
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, 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, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, 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 = os.Remove(testFilePath)
assert.NoError(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 = 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) {
usePubKey := false
u := getTestUser(usePubKey)
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)
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,
})
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)
err = os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileName1 := "test_file1.dat"
testFileSize := int64(131072)
testFileSize1 := int64(65535)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
dir1 := "dir1" //nolint:goconst
dir2 := "dir2" //nolint:goconst
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 2, 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, testFileSize+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 2, 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, testFileSize+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 2, 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, testFileSize+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 2, 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, testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 1, 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, testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 1, 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 = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(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 = 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) {
usePubKey := true
u := getTestUser(usePubKey)
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"
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,
})
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileName1 := "test_file1.dat"
testFileSize := int64(131072)
testFileSize1 := int64(65535)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
dir1 := "dir1"
dir2 := "dir2"
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, 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, testFileSize*2, f.UsedQuotaSize)
assert.Equal(t, 2, 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, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, 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, testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 1, 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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir2, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, dir2, testFileName), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize1*3+testFileSize*2, f.UsedQuotaSize)
assert.Equal(t, 5, 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, testFileSize1*2+testFileSize, f.UsedQuotaSize)
assert.Equal(t, 3, 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 = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(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 = 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) {
usePubKey := false
u := getTestUser(usePubKey)
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"
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,
})
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileName1 := "test_file1.dat"
testFileSize := int64(131072)
testFileSize1 := int64(65535)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
dir1 := "dir1"
dir2 := "dir2"
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 1, 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, testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 1, 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 = sftpUploadFile(testFilePath, path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, dir1, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 2, 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 = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(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 = 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) {
usePubKey := true
u := getTestUser(usePubKey)
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"
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,
})
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileName1 := "test_file1.dat"
testFileSize := int64(131072)
testFileSize1 := int64(65535)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
dir1 := "dir1"
dir2 := "dir2"
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
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 = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 1, 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 = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, 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, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, 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 = sftpUploadFile(testFilePath, path.Join(dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, 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, testFileSize*2+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 3, 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 = sftpUploadFile(testFilePath, path.Join(dir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, 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, testFileSize*2+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 3, 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 = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(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 = 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 TestVirtualFoldersLink(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
folderName1 := filepath.Base(mappedPath1)
vdirPath1 := "/vdir1"
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
folderName2 := filepath.Base(mappedPath2)
vdirPath2 := "/vdir2"
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,
})
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
testDir := "adir"
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, 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
assert.Error(t, err)
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testDir, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(path.Join(vdirPath1, testFileName), testFileName+".link1")
assert.Error(t, err)
err = client.Symlink(path.Join(vdirPath2, testFileName), testFileName+".link1")
assert.Error(t, err)
err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath1, testFileName+".link1"))
assert.Error(t, err)
err = os.Remove(testFilePath)
assert.NoError(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 = 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 TestVirtualFolderQuotaScan(t *testing.T) {
mappedPath := filepath.Join(os.TempDir(), "mapped_dir")
folderName := filepath.Base(mappedPath)
err := os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
testFileSize := int64(65535)
testFilePath := filepath.Join(mappedPath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
expectedQuotaSize := testFileSize
expectedQuotaFiles := 1
folder, _, err := httpdtest.AddFolder(vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}, http.StatusCreated)
assert.NoError(t, err)
_, err = httpdtest.StartFolderQuotaScan(folder, http.StatusAccepted)
assert.NoError(t, err)
assert.Eventually(t, func() bool {
scans, _, err := httpdtest.GetFoldersQuotaScans(http.StatusOK)
if err == nil {
return len(scans) == 0
}
return false
}, 1*time.Second, 50*time.Millisecond)
folder, _, err = httpdtest.GetFolderByName(folderName, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, folder.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize, folder.UsedQuotaSize)
_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
}
func TestVFolderMultipleQuotaScan(t *testing.T) {
folderName := "folder_name"
res := common.QuotaScans.AddVFolderQuotaScan(folderName)
assert.True(t, res)
res = common.QuotaScans.AddVFolderQuotaScan(folderName)
assert.False(t, res)
res = common.QuotaScans.RemoveVFolderQuotaScan(folderName)
assert.True(t, res)
activeScans := common.QuotaScans.GetVFoldersQuotaScans()
assert.Len(t, activeScans, 0)
res = common.QuotaScans.RemoveVFolderQuotaScan(folderName)
assert.False(t, res)
}
func TestVFolderQuotaSize(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
testFileSize := int64(131072)
u.QuotaFiles = 1
u.QuotaSize = testFileSize + 1
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
folderName1 := filepath.Base(mappedPath1)
vdirPath1 := "/vpath1"
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
folderName2 := filepath.Base(mappedPath2)
vdirPath2 := "/vpath2"
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,
QuotaFiles: 1,
QuotaSize: testFileSize * 2,
})
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
// vdir1 is included in the user quota so upload must fail
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
assert.Error(t, err)
// upload to vdir2 must work, it has its own quota
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName), testFileSize, client)
assert.NoError(t, err)
// now vdir2 is over quota
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+".quota"), testFileSize, client)
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, testFileSize, user.UsedQuotaSize)
// remove a file
err = client.Remove(testFileName)
assert.NoError(t, err)
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)
// upload to vdir1 must work now
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testFileName), testFileSize, client)
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, testFileSize, f.UsedQuotaSize)
assert.Equal(t, 1, 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)
}
// now create another user with the same shared folder but a different quota limit
u.Username = defaultUsername + "1"
u.VirtualFolders = nil
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
MappedPath: mappedPath2,
},
VirtualPath: vdirPath2,
QuotaFiles: 10,
QuotaSize: testFileSize*2 + 1,
})
user1, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err = getSftpClient(user1, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+".quota"), testFileSize, client)
assert.NoError(t, err)
// the folder is now over quota for size but not for files
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testFileName+".quota1"), testFileSize, client)
assert.Error(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user1, 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 TestMissingFile(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile("missing_file", localDownloadPath, 0, client)
assert.Error(t, err, "download missing file must fail")
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestOpenError(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
}
usePubKey := false
u := getTestUser(usePubKey)
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, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = os.Chmod(user.GetHomeDir(), 0001)
assert.NoError(t, err)
_, err = client.ReadDir(".")
assert.Error(t, err, "read dir must fail if we have no filesystem read permissions")
err = os.Chmod(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err)
testFileSize := int64(65535)
testFilePath := filepath.Join(user.GetHomeDir(), testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
_, err = client.Stat(testFileName)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err)
err = os.Chmod(testFilePath, 0001)
assert.NoError(t, err)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.Error(t, err, "file download must fail if we have no filesystem read permissions")
err = sftpUploadFile(localDownloadPath, testFileName, testFileSize, client)
assert.Error(t, err, "upload must fail if we have no filesystem write permissions")
testDir := "test"
err = client.Mkdir(testDir)
assert.NoError(t, err)
err = createTestFile(filepath.Join(user.GetHomeDir(), testDir, testFileName), testFileSize)
assert.NoError(t, err)
err = os.Chmod(user.GetHomeDir(), 0000)
assert.NoError(t, err)
_, err = client.Lstat(testFileName)
assert.Error(t, err, "file stat must fail if we have no filesystem read permissions")
err = sftpUploadFile(localDownloadPath, path.Join(testDir, testFileName), testFileSize, client)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.ReadLink(testFileName)
assert.ErrorIs(t, err, os.ErrPermission)
err = client.Remove(testFileName)
assert.ErrorIs(t, err, os.ErrPermission)
err = os.Chmod(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err)
err = os.Chmod(filepath.Join(user.GetHomeDir(), testDir), 0000)
assert.NoError(t, err)
err = client.Rename(testFileName, path.Join(testDir, testFileName))
assert.True(t, errors.Is(err, fs.ErrPermission))
err = os.Chmod(filepath.Join(user.GetHomeDir(), testDir), os.ModePerm)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestOverwriteDirWithFile(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileSize := int64(65535)
testDirName := "test_dir" //nolint:goconst
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = client.Mkdir(testDirName)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testDirName, testFileSize, client)
assert.Error(t, err, "copying a file over an existing dir must fail")
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName, testDirName)
assert.Error(t, err, "rename a file over an existing dir must fail")
err = client.RemoveDirectory(testDirName)
assert.NoError(t, err)
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestHashedPasswords(t *testing.T) {
usePubKey := false
plainPwd := "password"
pwdMapping := make(map[string]string)
pwdMapping["$argon2id$v=19$m=65536,t=3,p=2$xtcO/oRkC8O2Tn+mryl2mw$O7bn24f2kuSGRMi9s5Cm61Wqd810px1jDsAasrGWkzQ"] = plainPwd
pwdMapping["$pbkdf2-sha1$150000$DveVjgYUD05R$X6ydQZdyMeOvpgND2nqGR/0GGic="] = plainPwd
pwdMapping["$pbkdf2-sha256$150000$E86a9YMX3zC7$R5J62hsSq+pYw00hLLPKBbcGXmq7fj5+/M0IFoYtZbo="] = plainPwd
pwdMapping["$pbkdf2-sha512$150000$dsu7T5R3IaVQ$1hFXPO1ntRBcoWkSLKw+s4sAP09Xtu4Ya7CyxFq64jM9zdUg8eRJVr3NcR2vQgb0W9HHvZaILHsL4Q/Vr6arCg=="] = plainPwd
pwdMapping["$1$b5caebda$VODr/nyhGWgZaY8sJ4x05."] = plainPwd
pwdMapping["$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK"] = "secret"
pwdMapping["$6$459ead56b72e44bc$uog86fUxscjt28BZxqFBE2pp2QD8P/1e98MNF75Z9xJfQvOckZnQ/1YJqiq1XeytPuDieHZvDAMoP7352ELkO1"] = "secret"
pwdMapping["$5$h4Aalt0fJdGX8sgv$Rd2ew0fvgzUN.DzAVlKa9QL4q/DZWo4SsKpB9.3AyZ/"] = plainPwd
pwdMapping["$apr1$OBWLeSme$WoJbB736e7kKxMBIAqilb1"] = plainPwd
pwdMapping["{MD5}5f4dcc3b5aa765d61d8327deb882cf99"] = plainPwd
pwdMapping["{SHA256}5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"] = plainPwd
pwdMapping["{SHA512}b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86"] = plainPwd
for pwd, clearPwd := range pwdMapping {
u := getTestUser(usePubKey)
u.Password = pwd
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
user.Password = ""
userGetInitial, _, err := httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
user, err = dataprovider.UserExists(user.Username, "")
assert.NoError(t, err)
assert.Equal(t, pwd, user.Password)
user.Password = clearPwd
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err, "unable to login with password %q", pwd) {
assert.NoError(t, checkBasicSFTP(client))
conn.Close()
client.Close()
}
user.Password = pwd
conn, client, err = getSftpClient(user, usePubKey)
if !assert.Error(t, err, "login with wrong password must fail") {
client.Close()
conn.Close()
}
// the password must converted to bcrypt and we should still be able to login
user, err = dataprovider.UserExists(user.Username, "")
assert.NoError(t, err)
assert.True(t, strings.HasPrefix(user.Password, "$2a$"))
// update the user to invalidate the cached password and force a new check
user.Password = ""
userGet, _, err := httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
userGetInitial.LastLogin = userGet.LastLogin
userGetInitial.UpdatedAt = userGet.UpdatedAt
assert.Equal(t, userGetInitial, userGet)
// login should still work
user.Password = clearPwd
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err, "unable to login with password %q", pwd) {
assert.NoError(t, checkBasicSFTP(client))
conn.Close()
client.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
}
func TestPasswordsHashPbkdf2Sha256_389DS(t *testing.T) {
pbkdf389dsPwd := "{PBKDF2_SHA256}AAAIAMZIKG4ie44zJY4HOXI+upFR74PzWLUQV63jg+zzkbEjCK3N4qW583WF7EdcpeoOMQ4HY3aWEXB6lnXhXJixbJkU4vVSJkL6YCbU3TrD0qn1uUUVSkaIgAOtmZENitwbhYhiWfEzGyAtFqkFd75P5xhWJEog9XhQKYrR0f7S3WGGZq03JRcLJ460xpU97bE/sWRn7sshgkWzLuyrs0I+XRKmK7FJeaA9zd+1m44Y3IVmZ2YLdKATzjRHAIgpBC6i1TWOcpKJT1+feP1C9hrxH8vU9baw9thNiO8jSHaZlwb//KpJFe0ahVnG/1ubiG8cO0+CCqDqXVJR6Vr4QZxHP+4pwooW+4TP/L+HFdyA1y6z4gKfqYnBsmb3sD1R1TbxfH4btTdvgZAnBk9CmR3QASkFXxeTYsrmNd5+9IAHc6dm"
pbkdf389dsPwd = pbkdf389dsPwd[15:]
hashBytes, err := base64.StdEncoding.DecodeString(pbkdf389dsPwd)
assert.NoError(t, err)
iterBytes := hashBytes[0:4]
var iterations int32
err = binary.Read(bytes.NewBuffer(iterBytes), binary.BigEndian, &iterations)
assert.NoError(t, err)
salt := hashBytes[4:68]
targetKey := hashBytes[68:]
key := base64.StdEncoding.EncodeToString(targetKey)
pbkdf2Pwd := fmt.Sprintf("$pbkdf2-b64salt-sha256$%v$%v$%v", iterations, base64.StdEncoding.EncodeToString(salt), key)
pbkdf2ClearPwd := "password"
usePubKey := false
u := getTestUser(usePubKey)
u.Password = pbkdf2Pwd
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
user.Password = pbkdf2ClearPwd
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
assert.NoError(t, checkBasicSFTP(client))
}
user.Password = pbkdf2Pwd
conn, client, err = getSftpClient(user, usePubKey)
if !assert.Error(t, err, "login with wrong password must fail") {
client.Close()
conn.Close()
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermList(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
dataprovider.PermChown, dataprovider.PermChtimes}
u.Permissions["/sub"] = []string{dataprovider.PermCreateSymlinks, dataprovider.PermListItems}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
_, err = client.ReadDir(".")
assert.ErrorIs(t, err, os.ErrPermission, "read remote dir without permission should not succeed")
_, err = client.Stat("test_file")
assert.ErrorIs(t, err, os.ErrPermission, "stat remote file without permission should not succeed")
_, err = client.Lstat("test_file")
assert.ErrorIs(t, err, os.ErrPermission, "lstat remote file without permission should not succeed")
_, err = client.ReadLink("test_link")
assert.ErrorIs(t, err, os.ErrPermission, "read remote link without permission on source dir should not succeed")
_, err = client.RealPath(".")
assert.ErrorIs(t, err, os.ErrPermission, "real path without permission should not succeed")
f, err := client.Create(testFileName)
if assert.NoError(t, err) {
_, err = f.Write([]byte("content"))
assert.NoError(t, err)
err = f.Close()
assert.NoError(t, err)
}
err = client.Mkdir("sub")
assert.NoError(t, err)
err = client.Symlink(path.Join("/", testFileName), path.Join("/sub", testFileName))
assert.NoError(t, err)
_, err = client.ReadLink(path.Join("/sub", testFileName))
assert.Error(t, err, "read remote link without permission on targe dir should not succeed")
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermDownload(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.Error(t, err, "file download without permission should not succeed")
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermUpload(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermDelete, dataprovider.PermRename,
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.Error(t, err, "file upload without permission should not succeed")
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermOverwrite(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
dataprovider.PermRename, dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermChmod,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.Error(t, err, "file overwrite without permission should not succeed")
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermDelete(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermRename,
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Remove(testFileName)
assert.Error(t, err, "delete without permission should not succeed")
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
//nolint:dupl
func TestPermRename(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload,
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName, testFileName+".rename")
assert.True(t, errors.Is(err, fs.ErrPermission))
_, err = client.Stat(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
//nolint:dupl
func TestPermRenameOverwrite(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermChmod, dataprovider.PermRename,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName, testFileName+".rename")
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName, testFileName+".rename")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermCreateDirs(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
dataprovider.PermRename, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("testdir")
assert.Error(t, err, "mkdir without permission should not succeed")
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
//nolint:dupl
func TestPermSymlink(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
dataprovider.PermRename, dataprovider.PermCreateDirs, dataprovider.PermOverwrite, dataprovider.PermChmod, dataprovider.PermChown,
dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Symlink(testFilePath, testFilePath+".symlink")
assert.Error(t, err, "symlink without permission should not succeed")
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermChmod(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
dataprovider.PermRename, dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite,
dataprovider.PermChown, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Chmod(testFileName, os.ModePerm)
assert.Error(t, err, "chmod without permission should not succeed")
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
//nolint:dupl
func TestPermChown(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
dataprovider.PermRename, dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite,
dataprovider.PermChmod, dataprovider.PermChtimes}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Chown(testFileName, os.Getuid(), os.Getgid())
assert.Error(t, err, "chown without permission should not succeed")
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
//nolint:dupl
func TestPermChtimes(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
dataprovider.PermRename, dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite,
dataprovider.PermChmod, dataprovider.PermChown}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Chtimes(testFileName, time.Now(), time.Now())
assert.Error(t, err, "chtimes without permission should not succeed")
err = client.Remove(testFileName)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSubDirsUploads(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermAny}
u.Permissions["/subdir"] = []string{dataprovider.PermChtimes, dataprovider.PermDownload, dataprovider.PermOverwrite}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("subdir")
assert.NoError(t, err)
testFileNameSub := "/subdir/test_file_dat"
testSubFile := filepath.Join(user.GetHomeDir(), "subdir", "file.dat")
testDir := "testdir"
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testSubFile, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileNameSub, testFileSize, client)
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Symlink(testFileName, testFileNameSub+".link")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Symlink(testFileName, testFileName+".link")
assert.NoError(t, err)
err = client.Rename(testFileName, testFileNameSub+".rename")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Rename(testFileName, testFileName+".rename")
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
// rename overwriting an existing file
err = client.Rename(testFileName, testFileName+".rename")
assert.NoError(t, err)
// now try to overwrite a directory
err = client.Mkdir(testDir)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Rename(testFileName, testDir)
assert.Error(t, err)
err = client.Remove(testFileName)
assert.NoError(t, err)
err = client.Remove(testDir)
assert.NoError(t, err)
err = client.Remove(path.Join("/subdir", "file.dat"))
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Remove(testFileName + ".rename")
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSubDirsOverwrite(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermAny}
u.Permissions["/subdir"] = []string{dataprovider.PermOverwrite, dataprovider.PermListItems}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileName := "/subdir/test_file.dat" //nolint:goconst
testFilePath := filepath.Join(homeBasePath, "test_file.dat")
testFileSFTPPath := filepath.Join(u.GetHomeDir(), "subdir", "test_file.dat")
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFileSFTPPath, 16384)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName+".new", testFileSize, client)
assert.True(t, errors.Is(err, fs.ErrPermission))
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSubDirsDownloads(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermAny}
u.Permissions["/subdir"] = []string{dataprovider.PermChmod, dataprovider.PermUpload, dataprovider.PermListItems}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("subdir")
assert.NoError(t, err)
testFileName := "/subdir/test_file.dat"
testFilePath := filepath.Join(homeBasePath, "test_file.dat")
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.True(t, errors.Is(err, fs.ErrPermission))
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Chtimes(testFileName, time.Now(), time.Now())
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Rename(testFileName, testFileName+".rename")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Symlink(testFileName, testFileName+".link")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Remove(testFileName)
assert.True(t, errors.Is(err, fs.ErrPermission))
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermsSubDirsSetstat(t *testing.T) {
// for setstat we check the parent dir permission if the requested path is a dir
// otherwise the path permission
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermCreateDirs}
u.Permissions["/subdir"] = []string{dataprovider.PermAny}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("subdir")
assert.NoError(t, err)
testFileName := "/subdir/test_file.dat"
testFilePath := filepath.Join(homeBasePath, "test_file.dat")
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = client.Chtimes("/subdir/", time.Now(), time.Now())
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Chtimes("subdir/", time.Now(), time.Now())
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Chtimes(testFileName, time.Now(), time.Now())
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestOpenUnhandledChannel(t *testing.T) {
u := getTestUser(false)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
config := &ssh.ClientConfig{
User: user.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{ssh.Password(defaultPassword)},
Timeout: 5 * time.Second,
}
conn, err := ssh.Dial("tcp", sftpServerAddr, config)
if assert.NoError(t, err) {
_, _, err = conn.OpenChannel("unhandled", nil)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "unknown channel type")
}
err = conn.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 TestAlgorithmNotNegotiated(t *testing.T) {
u := getTestUser(false)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
config := &ssh.ClientConfig{
Config: ssh.Config{
Ciphers: []string{ssh.InsecureCipherRC4},
},
User: user.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{ssh.Password(defaultPassword)},
Timeout: 5 * time.Second,
}
_, err = ssh.Dial("tcp", sftpServerAddr, config)
if assert.Error(t, err) {
negotiationErr := &ssh.AlgorithmNegotiationError{}
assert.ErrorAs(t, err, &negotiationErr)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPermsSubDirsCommands(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermAny}
u.Permissions["/subdir"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermCreateDirs}
u.Permissions["/subdir/otherdir"] = []string{dataprovider.PermListItems, dataprovider.PermDownload}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Mkdir("subdir")
assert.NoError(t, err)
acmodTime := time.Now()
err = client.Chtimes("/subdir", acmodTime, acmodTime)
assert.NoError(t, err)
_, err = client.Stat("/subdir")
assert.NoError(t, err)
_, err = client.ReadDir("/")
assert.NoError(t, err)
_, err = client.ReadDir("/subdir")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.RemoveDirectory("/subdir/dir")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Mkdir("/subdir/otherdir/dir")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Mkdir("/otherdir")
assert.NoError(t, err)
err = client.Mkdir("/subdir/otherdir")
assert.NoError(t, err)
err = client.Rename("/otherdir", "/subdir/otherdir/adir")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Symlink("/otherdir", "/subdir/otherdir")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Symlink("/otherdir", "/otherdir_link")
assert.NoError(t, err)
err = client.Rename("/otherdir", "/otherdir1")
assert.NoError(t, err)
err = client.RemoveDirectory("/otherdir1")
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestRootDirCommands(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermAny}
u.Permissions["/subdir"] = []string{dataprovider.PermDownload, dataprovider.PermUpload}
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermAny}
u.Permissions["/subdir"] = []string{dataprovider.PermDownload, dataprovider.PermUpload}
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Rename("/", "rootdir")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.Symlink("/", "rootdir")
assert.True(t, errors.Is(err, fs.ErrPermission))
err = client.RemoveDirectory("/")
assert.True(t, errors.Is(err, fs.ErrPermission))
}
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
user.ID = 0
user.CreatedAt = 0
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermAny}
_, 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)
}
func TestRelativePaths(t *testing.T) {
user := getTestUser(true)
var path, rel string
filesystems := []vfs.Fs{vfs.NewOsFs("", user.GetHomeDir(), "", nil)}
keyPrefix := strings.TrimPrefix(user.GetHomeDir(), "/") + "/"
s3config := vfs.S3FsConfig{
BaseS3FsConfig: sdk.BaseS3FsConfig{
KeyPrefix: keyPrefix,
},
}
s3fs, _ := vfs.NewS3Fs("", user.GetHomeDir(), "", s3config)
gcsConfig := vfs.GCSFsConfig{
BaseGCSFsConfig: sdk.BaseGCSFsConfig{
KeyPrefix: keyPrefix,
},
}
gcsfs, _ := vfs.NewGCSFs("", user.GetHomeDir(), "", gcsConfig)
sftpconfig := vfs.SFTPFsConfig{
BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
Endpoint: sftpServerAddr,
Username: defaultUsername,
Prefix: keyPrefix,
},
Password: kms.NewPlainSecret(defaultPassword),
}
sftpfs, _ := vfs.NewSFTPFs("", "", os.TempDir(), []string{user.Username}, sftpconfig)
if runtime.GOOS != osWindows {
filesystems = append(filesystems, s3fs, gcsfs, sftpfs)
}
rootPath := "/"
for _, fs := range filesystems {
path = filepath.Join(user.HomeDir, "/")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, "//")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, "../..")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, "../../../../../")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, "/..")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, "/../../../..")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, "")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, ".")
rel = fs.GetRelativePath(path)
assert.Equal(t, rootPath, rel)
path = filepath.Join(user.HomeDir, "somedir")
rel = fs.GetRelativePath(path)
assert.Equal(t, "/somedir", rel)
path = filepath.Join(user.HomeDir, "/somedir/subdir")
rel = fs.GetRelativePath(path)
assert.Equal(t, "/somedir/subdir", rel)
}
}
func TestResolvePaths(t *testing.T) {
user := getTestUser(true)
var path, resolved string
var err error
filesystems := []vfs.Fs{vfs.NewOsFs("", user.GetHomeDir(), "", nil)}
keyPrefix := strings.TrimPrefix(user.GetHomeDir(), "/") + "/"
s3config := vfs.S3FsConfig{
BaseS3FsConfig: sdk.BaseS3FsConfig{
KeyPrefix: keyPrefix,
Bucket: "bucket",
Region: "us-east-1",
},
}
err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err)
s3fs, err := vfs.NewS3Fs("", user.GetHomeDir(), "", s3config)
assert.NoError(t, err)
gcsConfig := vfs.GCSFsConfig{
BaseGCSFsConfig: sdk.BaseGCSFsConfig{
KeyPrefix: keyPrefix,
},
}
gcsfs, _ := vfs.NewGCSFs("", user.GetHomeDir(), "", gcsConfig)
if runtime.GOOS != osWindows {
filesystems = append(filesystems, s3fs, gcsfs)
}
for _, fs := range filesystems {
path = "/"
resolved, _ = fs.ResolvePath(filepath.ToSlash(path))
assert.Equal(t, fs.Join(user.GetHomeDir(), "/"), resolved)
path = "."
resolved, _ = fs.ResolvePath(filepath.ToSlash(path))
assert.Equal(t, fs.Join(user.GetHomeDir(), "/"), resolved)
path = "test/sub"
resolved, _ = fs.ResolvePath(filepath.ToSlash(path))
assert.Equal(t, fs.Join(user.GetHomeDir(), "/test/sub"), resolved)
path = "../test/sub"
resolved, err = fs.ResolvePath(filepath.ToSlash(path))
if vfs.IsLocalOsFs(fs) {
assert.Error(t, err, "Unexpected resolved path: %v for: %v, fs: %v", resolved, path, fs.Name())
} else {
assert.Equal(t, fs.Join(user.GetHomeDir(), "/test/sub"), resolved)
}
path = "../../../test/../sub"
resolved, err = fs.ResolvePath(filepath.ToSlash(path))
if vfs.IsLocalOsFs(fs) {
assert.Error(t, err, "Unexpected resolved path: %v for: %v, fs: %v", resolved, path, fs.Name())
} else {
assert.Equal(t, fs.Join(user.GetHomeDir(), "/sub"), resolved)
}
}
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestVirtualRelativePaths(t *testing.T) {
user := getTestUser(true)
mappedPath := filepath.Join(os.TempDir(), "mdir")
vdirPath := "/vdir" //nolint:goconst
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath,
},
VirtualPath: vdirPath,
})
err := os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
fsRoot := vfs.NewOsFs("", user.GetHomeDir(), "", nil)
fsVdir := vfs.NewOsFs("", mappedPath, vdirPath, nil)
rel := fsVdir.GetRelativePath(mappedPath)
assert.Equal(t, vdirPath, rel)
rel = fsRoot.GetRelativePath(filepath.Join(mappedPath, ".."))
assert.Equal(t, "/", rel)
// path outside home and virtual dir
rel = fsRoot.GetRelativePath(filepath.Join(mappedPath, "../vdir1"))
assert.Equal(t, "/", rel)
rel = fsVdir.GetRelativePath(filepath.Join(mappedPath, "../vdir1"))
assert.Equal(t, "/vdir", rel)
rel = fsVdir.GetRelativePath(filepath.Join(mappedPath, "file.txt"))
assert.Equal(t, "/vdir/file.txt", rel)
rel = fsRoot.GetRelativePath(filepath.Join(user.HomeDir, "vdir1/file.txt"))
assert.Equal(t, "/vdir1/file.txt", rel)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
}
func TestUserPerms(t *testing.T) {
user := getTestUser(true)
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermListItems}
user.Permissions["/p"] = []string{dataprovider.PermDelete}
user.Permissions["/p/1"] = []string{dataprovider.PermDownload, dataprovider.PermUpload}
user.Permissions["/p/2"] = []string{dataprovider.PermCreateDirs}
user.Permissions["/p/3"] = []string{dataprovider.PermChmod}
user.Permissions["/p/3/4"] = []string{dataprovider.PermChtimes}
user.Permissions["/tmp"] = []string{dataprovider.PermRename}
assert.True(t, user.HasPerm(dataprovider.PermListItems, "/"))
assert.True(t, user.HasPerm(dataprovider.PermListItems, "."))
assert.True(t, user.HasPerm(dataprovider.PermListItems, ""))
assert.True(t, user.HasPerm(dataprovider.PermListItems, "../"))
// path p and /p are the same
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/p"))
assert.True(t, user.HasPerm(dataprovider.PermDownload, "/p/1"))
assert.True(t, user.HasPerm(dataprovider.PermCreateDirs, "p/2"))
assert.True(t, user.HasPerm(dataprovider.PermChmod, "/p/3"))
assert.True(t, user.HasPerm(dataprovider.PermChtimes, "p/3/4/"))
assert.True(t, user.HasPerm(dataprovider.PermChtimes, "p/3/4/../4"))
// undefined paths have permissions of the nearest path
assert.True(t, user.HasPerm(dataprovider.PermListItems, "/p34"))
assert.True(t, user.HasPerm(dataprovider.PermListItems, "/p34/p1/file.dat"))
assert.True(t, user.HasPerm(dataprovider.PermChtimes, "/p/3/4/5/6"))
assert.True(t, user.HasPerm(dataprovider.PermDownload, "/p/1/test/file.dat"))
}
func TestWildcardPermissions(t *testing.T) {
user := getTestUser(true)
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermListItems}
user.Permissions["/p*"] = []string{dataprovider.PermDelete}
user.Permissions["/p/*"] = []string{dataprovider.PermDownload, dataprovider.PermUpload}
user.Permissions["/p/2"] = []string{dataprovider.PermCreateDirs}
user.Permissions["/pa"] = []string{dataprovider.PermChmod}
user.Permissions["/p/3/4"] = []string{dataprovider.PermChtimes}
assert.True(t, user.HasPerm(dataprovider.PermListItems, "/"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/p1"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/ppppp"))
assert.False(t, user.HasPerm(dataprovider.PermDelete, "/pa"))
assert.True(t, user.HasPerm(dataprovider.PermChmod, "/pa"))
assert.True(t, user.HasPerm(dataprovider.PermUpload, "/p/1"))
assert.True(t, user.HasPerm(dataprovider.PermUpload, "/p/p"))
assert.False(t, user.HasPerm(dataprovider.PermUpload, "/p/2"))
assert.True(t, user.HasPerm(dataprovider.PermDownload, "/p/3"))
assert.True(t, user.HasPerm(dataprovider.PermDownload, "/p/a/a/a"))
assert.False(t, user.HasPerm(dataprovider.PermDownload, "/p/3/4"))
assert.True(t, user.HasPerm(dataprovider.PermChtimes, "/p/3/4"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/pb/a/a/a"))
assert.False(t, user.HasPerm(dataprovider.PermDelete, "/abc/a/a/a"))
assert.True(t, user.HasPerm(dataprovider.PermListItems, "/abc/a/a/a/b"))
}
func TestRootWildcardPerms(t *testing.T) {
user := getTestUser(true)
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermListItems}
user.Permissions["/*"] = []string{dataprovider.PermDelete}
user.Permissions["/p/*"] = []string{dataprovider.PermDownload, dataprovider.PermUpload}
user.Permissions["/p/2"] = []string{dataprovider.PermCreateDirs}
user.Permissions["/pa"] = []string{dataprovider.PermChmod}
user.Permissions["/p/3/4"] = []string{dataprovider.PermChtimes}
assert.True(t, user.HasPerm(dataprovider.PermListItems, "/"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/p1"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/ppppp"))
assert.False(t, user.HasPerm(dataprovider.PermDelete, "/pa"))
assert.True(t, user.HasPerm(dataprovider.PermChmod, "/pa"))
assert.True(t, user.HasPerm(dataprovider.PermUpload, "/p/1"))
assert.True(t, user.HasPerm(dataprovider.PermUpload, "/p/p"))
assert.False(t, user.HasPerm(dataprovider.PermUpload, "/p/2"))
assert.True(t, user.HasPerm(dataprovider.PermCreateDirs, "/p/2"))
assert.True(t, user.HasPerm(dataprovider.PermCreateDirs, "/p/2/a"))
assert.True(t, user.HasPerm(dataprovider.PermDownload, "/p/3"))
assert.True(t, user.HasPerm(dataprovider.PermDownload, "/p/a/a/a"))
assert.False(t, user.HasPerm(dataprovider.PermDownload, "/p/3/4"))
assert.True(t, user.HasPerm(dataprovider.PermChtimes, "/p/3/4"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/pb/a/a/a"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/abc/a/a/a"))
assert.False(t, user.HasPerm(dataprovider.PermListItems, "/abc/a/a/a/b"))
assert.True(t, user.HasPerm(dataprovider.PermDelete, "/abc/a/a/a/b"))
}
func TestFilterFilePatterns(t *testing.T) {
user := getTestUser(true)
pattern := sdk.PatternsFilter{
Path: "/test",
AllowedPatterns: []string{"*.jpg", "*.png"},
DeniedPatterns: []string{"*.pdf"},
}
filters := dataprovider.UserFilters{
BaseUserFilters: sdk.BaseUserFilters{
FilePatterns: []sdk.PatternsFilter{pattern},
},
}
user.Filters = filters
ok, _ := user.IsFileAllowed("/test/test.jPg")
assert.True(t, ok)
ok, _ = user.IsFileAllowed("/test/test.pdf")
assert.False(t, ok)
ok, _ = user.IsFileAllowed("/test.pDf")
assert.True(t, ok)
filters.FilePatterns = append(filters.FilePatterns, sdk.PatternsFilter{
Path: "/",
AllowedPatterns: []string{"*.zip", "*.rar", "*.pdf"},
DeniedPatterns: []string{"*.gz"},
})
user.Filters = filters
ok, _ = user.IsFileAllowed("/test1/test.gz")
assert.False(t, ok)
ok, _ = user.IsFileAllowed("/test1/test.zip")
assert.True(t, ok)
ok, _ = user.IsFileAllowed("/test/sub/test.pdf")
assert.False(t, ok)
ok, _ = user.IsFileAllowed("/test1/test.png")
assert.False(t, ok)
filters.FilePatterns = append(filters.FilePatterns, sdk.PatternsFilter{
Path: "/test/sub",
DeniedPatterns: []string{"*.tar"},
})
user.Filters = filters
ok, _ = user.IsFileAllowed("/test/sub/sub/test.tar")
assert.False(t, ok)
ok, _ = user.IsFileAllowed("/test/sub/test.gz")
assert.True(t, ok)
ok, _ = user.IsFileAllowed("/test/test.zip")
assert.False(t, ok)
}
func TestUserAllowedLoginMethods(t *testing.T) {
user := getTestUser(true)
user.Filters.DeniedLoginMethods = dataprovider.ValidLoginMethods
allowedMethods := user.GetAllowedLoginMethods()
assert.Equal(t, 0, len(allowedMethods))
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
}
allowedMethods = user.GetAllowedLoginMethods()
assert.Equal(t, 4, len(allowedMethods))
assert.True(t, util.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndKeyboardInt))
assert.True(t, util.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndPassword))
}
func TestUserPartialAuth(t *testing.T) {
user := getTestUser(true)
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
}
assert.True(t, user.IsPartialAuth())
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodKeyboardInteractive,
}
assert.False(t, user.IsPartialAuth())
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
}
assert.False(t, user.IsPartialAuth())
user.Filters.DeniedLoginMethods = []string{
dataprovider.SSHLoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
}
assert.True(t, user.IsPartialAuth())
}
func TestUserGetNextAuthMethods(t *testing.T) {
user := getTestUser(true)
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
}
methods := user.GetNextAuthMethods()
require.Len(t, methods, 2)
assert.Equal(t, dataprovider.LoginMethodPassword, methods[0])
assert.Equal(t, dataprovider.SSHLoginMethodKeyboardInteractive, methods[1])
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
dataprovider.SSHLoginMethodKeyAndKeyboardInt,
}
methods = user.GetNextAuthMethods()
require.Len(t, methods, 1)
assert.Equal(t, dataprovider.LoginMethodPassword, methods[0])
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
dataprovider.SSHLoginMethodKeyAndPassword,
}
methods = user.GetNextAuthMethods()
require.Len(t, methods, 1)
assert.Equal(t, dataprovider.SSHLoginMethodKeyboardInteractive, methods[0])
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyAndPassword,
dataprovider.SSHLoginMethodKeyAndKeyboardInt,
}
methods = user.GetNextAuthMethods()
require.Len(t, methods, 0)
}
func TestUserIsLoginMethodAllowed(t *testing.T) {
user := getTestUser(true)
user.Filters.DeniedLoginMethods = []string{
dataprovider.LoginMethodPassword,
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
}
assert.False(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolSSH))
assert.False(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolFTP))
assert.False(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolWebDAV))
assert.False(t, user.IsLoginMethodAllowed(dataprovider.SSHLoginMethodPublicKey, common.ProtocolSSH))
assert.False(t, user.IsLoginMethodAllowed(dataprovider.SSHLoginMethodKeyboardInteractive, common.ProtocolSSH))
user.Filters.DeniedLoginMethods = []string{
dataprovider.SSHLoginMethodPublicKey,
dataprovider.SSHLoginMethodKeyboardInteractive,
}
assert.True(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolSSH))
user.Filters.DeniedLoginMethods = []string{
dataprovider.SSHLoginMethodPassword,
}
assert.True(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolHTTP))
assert.True(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolFTP))
assert.True(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolWebDAV))
assert.False(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolSSH))
}
func TestUserEmptySubDirPerms(t *testing.T) {
user := getTestUser(true)
user.Permissions = make(map[string][]string)
user.Permissions["/emptyperms"] = []string{}
for _, p := range dataprovider.ValidPerms {
assert.False(t, user.HasPerm(p, "/emptyperms"))
}
}
func TestUserFiltersIPMaskConditions(t *testing.T) {
user := getTestUser(true)
// with no filter login must be allowed even if the remoteIP is invalid
assert.True(t, user.IsLoginFromAddrAllowed("192.168.1.5"))
assert.True(t, user.IsLoginFromAddrAllowed("invalid"))
user.Filters.DeniedIP = append(user.Filters.DeniedIP, "192.168.1.0/24")
assert.False(t, user.IsLoginFromAddrAllowed("192.168.1.5"))
assert.True(t, user.IsLoginFromAddrAllowed("192.168.2.6"))
user.Filters.AllowedIP = append(user.Filters.AllowedIP, "192.168.1.5/32")
// if the same ip/mask is both denied and allowed then login must be allowed
assert.True(t, user.IsLoginFromAddrAllowed("192.168.1.5"))
assert.False(t, user.IsLoginFromAddrAllowed("192.168.1.3"))
assert.False(t, user.IsLoginFromAddrAllowed("192.168.3.6"))
user.Filters.DeniedIP = []string{}
assert.True(t, user.IsLoginFromAddrAllowed("192.168.1.5"))
assert.False(t, user.IsLoginFromAddrAllowed("192.168.1.6"))
user.Filters.DeniedIP = []string{"192.168.0.0/16", "172.16.0.0/16"}
user.Filters.AllowedIP = []string{}
assert.False(t, user.IsLoginFromAddrAllowed("192.168.5.255"))
assert.False(t, user.IsLoginFromAddrAllowed("172.16.1.2"))
assert.True(t, user.IsLoginFromAddrAllowed("172.18.2.1"))
user.Filters.AllowedIP = []string{"10.4.4.0/24"}
assert.False(t, user.IsLoginFromAddrAllowed("10.5.4.2"))
assert.True(t, user.IsLoginFromAddrAllowed("10.4.4.2"))
assert.True(t, user.IsLoginFromAddrAllowed("invalid"))
}
func TestGetVirtualFolderForPath(t *testing.T) {
user := getTestUser(true)
mappedPath1 := filepath.Join(os.TempDir(), "vpath1")
mappedPath2 := filepath.Join(os.TempDir(), "vpath1")
mappedPath3 := filepath.Join(os.TempDir(), "vpath3")
vdirPath := "/vdir/sub"
vSubDirPath := path.Join(vdirPath, "subdir", "subdir")
vSubDir1Path := path.Join(vSubDirPath, "subdir", "subdir")
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath1,
},
VirtualPath: vdirPath,
})
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath2,
},
VirtualPath: vSubDir1Path,
})
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
MappedPath: mappedPath3,
},
VirtualPath: vSubDirPath,
})
folder, err := user.GetVirtualFolderForPath(path.Join(vSubDirPath, "file"))
assert.NoError(t, err)
assert.Equal(t, folder.MappedPath, mappedPath3)
_, err = user.GetVirtualFolderForPath("/file")
assert.Error(t, err)
folder, err = user.GetVirtualFolderForPath(path.Join(vdirPath, "/file"))
assert.NoError(t, err)
assert.Equal(t, folder.MappedPath, mappedPath1)
folder, err = user.GetVirtualFolderForPath(path.Join(vSubDirPath+"1", "file"))
assert.NoError(t, err)
assert.Equal(t, folder.MappedPath, mappedPath1)
_, err = user.GetVirtualFolderForPath("/vdir/sub1/file")
assert.Error(t, err)
folder, err = user.GetVirtualFolderForPath(vdirPath)
assert.NoError(t, err)
}
func TestStatVFS(t *testing.T) {
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(65535)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
stat, err := client.StatVFS("/")
assert.NoError(t, err)
assert.Greater(t, stat.ID, uint32(0))
assert.Greater(t, stat.Blocks, uint64(0))
assert.Greater(t, stat.Bsize, uint64(0))
_, err = client.StatVFS("missing-path")
assert.Error(t, err)
assert.True(t, errors.Is(err, fs.ErrNotExist))
}
user.QuotaFiles = 100
user.Filters.DisableFsChecks = true
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
_, err = client.StatVFS("missing-path")
assert.Error(t, err)
assert.ErrorIs(t, err, fs.ErrNotExist)
stat, err := client.StatVFS("/")
assert.NoError(t, err)
assert.Greater(t, stat.ID, uint32(0))
assert.Greater(t, stat.Blocks, uint64(0))
assert.Greater(t, stat.Bsize, uint64(0))
assert.Equal(t, uint64(100), stat.Files)
assert.Equal(t, uint64(99), stat.Ffree)
}
user.QuotaSize = 8192
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
stat, err := client.StatVFS("/")
assert.NoError(t, err)
assert.Greater(t, stat.ID, uint32(0))
assert.Greater(t, stat.Blocks, uint64(0))
assert.Greater(t, stat.Bsize, uint64(0))
assert.Equal(t, uint64(100), stat.Files)
assert.Equal(t, uint64(0), stat.Ffree)
assert.Equal(t, uint64(2), stat.Blocks)
assert.Equal(t, uint64(0), stat.Bfree)
}
user.QuotaFiles = 0
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
stat, err := client.StatVFS("/")
assert.NoError(t, err)
assert.Greater(t, stat.ID, uint32(0))
assert.Greater(t, stat.Blocks, uint64(0))
assert.Greater(t, stat.Bsize, uint64(0))
assert.Greater(t, stat.Files, uint64(0))
assert.Equal(t, uint64(0), stat.Ffree)
assert.Equal(t, uint64(2), stat.Blocks)
assert.Equal(t, uint64(0), stat.Bfree)
}
user.QuotaSize = 1
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
stat, err := client.StatVFS("/")
assert.NoError(t, err)
assert.Greater(t, stat.ID, uint32(0))
assert.Equal(t, uint64(1), stat.Blocks)
assert.Equal(t, uint64(1), stat.Bsize)
assert.Greater(t, stat.Files, uint64(0))
assert.Equal(t, uint64(0), stat.Ffree)
assert.Equal(t, uint64(1), stat.Blocks)
assert.Equal(t, uint64(0), stat.Bfree)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestStatVFSCloudBackend(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
u.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("https://myaccount.blob.core.windows.net/sasurl")
user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err, string(resp))
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = dataprovider.UpdateUserQuota(&user, 100, 8192, true)
assert.NoError(t, err)
stat, err := client.StatVFS("/")
assert.NoError(t, err)
assert.Greater(t, stat.ID, uint32(0))
assert.Greater(t, stat.Blocks, uint64(0))
assert.Greater(t, stat.Bsize, uint64(0))
assert.Equal(t, uint64(1000000+100), stat.Files)
assert.Equal(t, uint64(2147483648+2), stat.Blocks)
assert.Equal(t, uint64(1000000), stat.Ffree)
assert.Equal(t, uint64(2147483648), stat.Bfree)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSSHCommands(t *testing.T) {
usePubKey := false
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
_, err = runSSHCommand("ls", user, usePubKey)
assert.Error(t, err, "unsupported ssh command must fail")
_, err = runSSHCommand("cd", user, usePubKey)
assert.NoError(t, err)
out, err := runSSHCommand("pwd", user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "/\n", string(out))
}
out, err = runSSHCommand("md5sum", user, usePubKey)
assert.NoError(t, err)
// echo -n '' | md5sum
assert.Contains(t, string(out), "d41d8cd98f00b204e9800998ecf8427e")
out, err = runSSHCommand("sha1sum", user, usePubKey)
assert.NoError(t, err)
assert.Contains(t, string(out), "da39a3ee5e6b4b0d3255bfef95601890afd80709")
out, err = runSSHCommand("sha256sum", user, usePubKey)
assert.NoError(t, err)
assert.Contains(t, string(out), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
out, err = runSSHCommand("sha384sum", user, usePubKey)
assert.NoError(t, err)
assert.Contains(t, string(out), "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b")
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSSHFileHash(t *testing.T) {
usePubKey := true
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
u := getTestUserWithCryptFs(usePubKey)
u.Username = u.Username + "_crypt"
cryptUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser, cryptUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
user.Permissions = make(map[string][]string)
user.Permissions["/"] = []string{dataprovider.PermUpload}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
_, err = runSSHCommand("sha512sum "+testFileName, user, usePubKey)
assert.Error(t, err, "hash command with no list permission must fail")
user.Permissions["/"] = []string{dataprovider.PermAny}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
initialHash, err := computeHashForFile(sha512.New(), testFilePath)
assert.NoError(t, err)
out, err := runSSHCommand("sha512sum "+testFileName, user, usePubKey)
if assert.NoError(t, err) {
assert.Contains(t, string(out), initialHash)
}
_, err = runSSHCommand("sha512sum invalid_path", user, usePubKey)
assert.Error(t, err, "hash for an invalid path must fail")
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
}
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(cryptUser, 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)
}
func TestSSHCopy(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.QuotaFiles = 100
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
folderName1 := filepath.Base(mappedPath1)
vdirPath1 := "/vdir1/subdir"
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
folderName2 := filepath.Base(mappedPath2)
vdirPath2 := "/vdir2/subdir"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName1,
},
VirtualPath: vdirPath1,
QuotaFiles: -1,
QuotaSize: -1,
})
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
},
VirtualPath: vdirPath2,
QuotaFiles: 100,
QuotaSize: 0,
})
u.Filters.FilePatterns = []sdk.PatternsFilter{
{
Path: "/",
DeniedPatterns: []string{"*.denied"},
},
}
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testDir := "adir"
testDir1 := "adir1"
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileName1 := "test_file1.dat"
testFileSize1 := int64(65537)
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
err = client.Mkdir(testDir)
assert.NoError(t, err)
err = client.Mkdir(path.Join(vdirPath1, testDir1))
assert.NoError(t, err)
err = client.Mkdir(path.Join(vdirPath2, testDir1))
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testDir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, testDir1, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testDir1, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, testDir1, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = client.Symlink(path.Join(testDir, testFileName), testFileName)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 4, user.UsedQuotaFiles)
assert.Equal(t, 2*testFileSize+2*testFileSize1, user.UsedQuotaSize)
f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 2, 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 = client.Stat(testDir1)
assert.Error(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s", path.Join(vdirPath1, testDir1)), user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand("sftpgo-copy", user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", testFileName, testFileName+".linkcopy"), user, usePubKey)
assert.Error(t, err)
out, err := runSSHCommand(fmt.Sprintf("sftpgo-copy %s %s", path.Join(vdirPath1, testDir1), "."), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
fi, err := client.Stat(testDir1)
if assert.NoError(t, err) {
assert.True(t, fi.IsDir())
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 6, user.UsedQuotaFiles)
assert.Equal(t, 3*testFileSize+3*testFileSize1, user.UsedQuotaSize)
}
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", "missing\\ dir", "."), user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath1, testDir1), "."), user, usePubKey)
if assert.NoError(t, err) {
// all files are overwritten, quota must remain unchanged
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 6, user.UsedQuotaFiles)
assert.Equal(t, 3*testFileSize+3*testFileSize1, user.UsedQuotaSize)
}
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath2, testDir1, testFileName), testFileName+".copy"), //nolint:goconst
user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
fi, err := client.Stat(testFileName + ".copy") //nolint:goconst
if assert.NoError(t, err) {
assert.True(t, fi.Mode().IsRegular())
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 7, user.UsedQuotaFiles)
assert.Equal(t, 4*testFileSize+3*testFileSize1, user.UsedQuotaSize)
}
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath1, testDir1), path.Join(vdirPath2, testDir1+"copy")), //nolint:goconst
user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
fi, err := client.Stat(path.Join(vdirPath2, testDir1+"copy"))
if assert.NoError(t, err) {
assert.True(t, fi.IsDir())
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 7, user.UsedQuotaFiles)
assert.Equal(t, 4*testFileSize+3*testFileSize1, user.UsedQuotaSize)
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, testFileSize*2+testFileSize1*2, f.UsedQuotaSize)
assert.Equal(t, 4, f.UsedQuotaFiles)
}
out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath1, testDir1), path.Join(vdirPath1, testDir1+"copy")),
user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
_, err := client.Stat(path.Join(vdirPath2, testDir1+"copy"))
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 9, user.UsedQuotaFiles)
assert.Equal(t, 5*testFileSize+4*testFileSize1, user.UsedQuotaSize)
f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 2*testFileSize+2*testFileSize1, f.UsedQuotaSize)
assert.Equal(t, 4, f.UsedQuotaFiles)
}
// cross folder copy
newDir := "newdir"
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath2, ".."), newDir), user, usePubKey)
assert.NoError(t, err)
_, err = client.Stat(newDir)
assert.NoError(t, err)
// denied pattern
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(testDir, testFileName), testFileName+".denied"), user, usePubKey)
assert.Error(t, err)
if runtime.GOOS != osWindows {
subPath := filepath.Join(mappedPath1, testDir1, "asubdir", "anothersub", "another")
err = os.MkdirAll(subPath, os.ModePerm)
assert.NoError(t, err)
err = os.Chmod(subPath, 0001)
assert.NoError(t, err)
// listing contents for subdirs with no permissions will fail
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", vdirPath1, "newdir1"), user, usePubKey)
assert.Error(t, err)
err = os.Chmod(subPath, os.ModePerm)
assert.NoError(t, err)
err = os.Chmod(filepath.Join(user.GetHomeDir(), testDir1), 0555)
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath1, testDir1, testFileName),
path.Join(testDir1, "anewdir")), user, usePubKey)
assert.Error(t, err)
err = os.Chmod(filepath.Join(user.GetHomeDir(), testDir1), os.ModePerm)
assert.NoError(t, err)
err = os.Chmod(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err)
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(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 = 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 TestSSHCopyPermissions(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.Permissions["/dir1"] = []string{dataprovider.PermUpload, dataprovider.PermDownload, dataprovider.PermListItems}
u.Permissions["/dir2"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermDownload,
dataprovider.PermListItems, dataprovider.PermCopy}
u.Permissions["/dir3"] = []string{dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermDownload,
dataprovider.PermListItems}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testDir := "tDir"
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = client.Mkdir(testDir)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join("/", testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
// test copy file with no permission
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join("/", testDir, testFileName), path.Join("/dir3", testFileName)),
user, usePubKey)
assert.Error(t, err)
// test copy dir with no create dirs perm
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join("/", testDir), "/dir1/"), user, usePubKey)
assert.Error(t, err)
// dir2 has the needed permissions
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join("/", testDir), "/dir2/"), user, usePubKey)
assert.NoError(t, err)
info, err := client.Stat(path.Join("/dir2", testDir))
if assert.NoError(t, err) {
assert.True(t, info.IsDir())
}
info, err = client.Stat(path.Join("/dir2", testDir, testFileName))
if assert.NoError(t, err) {
assert.True(t, info.Mode().IsRegular())
}
// now create a symlink, dir2 has no create symlink permission, but symlinks will be ignored
err = client.Symlink(path.Join("/", testDir, testFileName), path.Join("/", testDir, testFileName+".link"))
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join("/", testDir), "/dir2/sub"), user, usePubKey)
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join("/", testDir), "/newdir"), user, usePubKey)
assert.NoError(t, err)
// now delete the file and copy inside /dir3
err = client.Remove(path.Join("/", testDir, testFileName))
assert.NoError(t, err)
// the symlink will be skipped, so no errors
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join("/", testDir), "/dir3"), user, usePubKey)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSSHCopyQuotaLimits(t *testing.T) {
usePubKey := true
testFileSize := int64(131072)
testFileSize1 := int64(65536)
testFileSize2 := int64(32768)
u := getTestUser(usePubKey)
u.QuotaFiles = 3
u.QuotaSize = testFileSize + testFileSize1 + 1
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
folderName1 := filepath.Base(mappedPath1)
vdirPath1 := "/vdir1"
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
folderName2 := filepath.Base(mappedPath2)
vdirPath2 := "/vdir2"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName1,
},
VirtualPath: vdirPath1,
QuotaFiles: -1,
QuotaSize: -1,
})
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
},
VirtualPath: vdirPath2,
QuotaFiles: 3,
QuotaSize: testFileSize + testFileSize1 + 1,
})
u.Filters.FilePatterns = []sdk.PatternsFilter{
{
Path: "/",
DeniedPatterns: []string{"*.denied"},
},
}
err := os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
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)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testDir := "testDir"
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
testFileName2 := "test_file2.dat"
testFilePath2 := filepath.Join(homeBasePath, testFileName2)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
err = createTestFile(testFilePath2, testFileSize2)
assert.NoError(t, err)
err = client.Mkdir(testDir)
assert.NoError(t, err)
err = client.Mkdir(path.Join(vdirPath2, testDir))
assert.NoError(t, err)
err = sftpUploadFile(testFilePath2, path.Join(testDir, testFileName2), testFileSize2, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath2, path.Join(testDir, testFileName2+".dupl"), testFileSize2, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath2, path.Join(vdirPath2, testDir, testFileName2), testFileSize2, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath2, path.Join(vdirPath2, testDir, testFileName2+".dupl"), testFileSize2, client)
assert.NoError(t, err)
// user quota: 2 files, size: 32768*2, folder2 quota: 2 files, size: 32768*2
// try to duplicate testDir, this will result in 4 file (over quota) and 32768*4 bytes (not over quota)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", testDir, testDir+"_copy"), user, usePubKey) //nolint:goconst
assert.Error(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath2, testDir),
path.Join(vdirPath2, testDir+"_copy")), user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", testDir), user, usePubKey)
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath2, testDir)), user, usePubKey)
assert.NoError(t, err)
// remove partially copied dirs
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", testDir+"_copy"), user, usePubKey)
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath2, testDir+"_copy")), user, usePubKey)
assert.NoError(t, err)
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)
f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 0, f.UsedQuotaFiles)
assert.Equal(t, int64(0), f.UsedQuotaSize)
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 0, f.UsedQuotaFiles)
assert.Equal(t, int64(0), f.UsedQuotaSize)
err = client.Mkdir(path.Join(vdirPath1, testDir))
assert.NoError(t, err)
err = client.Mkdir(path.Join(vdirPath2, testDir))
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
// vdir1 is included in user quota, file limit will be exceeded
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath1, testDir), "/"), user, usePubKey)
assert.Error(t, err)
// vdir2 size limit will be exceeded
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath1, testDir, testFileName),
vdirPath2+"/"), user, usePubKey)
assert.Error(t, err)
// now decrease the limits
user.QuotaFiles = 1
user.QuotaSize = testFileSize * 10
for idx, f := range user.VirtualFolders {
if f.Name == folderName2 {
user.VirtualFolders[idx].QuotaSize = testFileSize
user.VirtualFolders[idx].QuotaFiles = 10
}
}
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
assert.Equal(t, 1, user.QuotaFiles)
assert.Equal(t, testFileSize*10, user.QuotaSize)
if assert.Len(t, user.VirtualFolders, 2) {
for _, f := range user.VirtualFolders {
if f.Name == folderName2 {
assert.Equal(t, testFileSize, f.QuotaSize)
assert.Equal(t, 10, f.QuotaFiles)
}
}
}
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath1, testDir),
path.Join(vdirPath2, testDir+".copy")), user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath2, testDir),
testDir+".copy"), user, usePubKey)
assert.Error(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
err = os.Remove(testFilePath2)
assert.NoError(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 = 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 TestSSHRemove(t *testing.T) {
usePubKey := false
u := getTestUser(usePubKey)
u.QuotaFiles = 100
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
folderName1 := filepath.Base(mappedPath1)
vdirPath1 := "/vdir1/sub"
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
folderName2 := filepath.Base(mappedPath2)
vdirPath2 := "/vdir2/sub"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName1,
},
VirtualPath: vdirPath1,
QuotaFiles: -1,
QuotaSize: -1,
})
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
},
VirtualPath: vdirPath2,
QuotaFiles: 100,
QuotaSize: 0,
})
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)
err = os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileName1 := "test_file1.dat"
testFileSize1 := int64(65537)
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
testDir := "testdir"
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize1)
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 = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath1, testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath1, testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, path.Join(vdirPath2, testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath1, path.Join(vdirPath2, testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = client.Symlink(testFileName, testFileName+".link")
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", testFileName+".link"), user, usePubKey)
assert.NoError(t, err)
_, err = runSSHCommand("sftpgo-remove /vdir1", user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand("sftpgo-remove /", user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand("sftpgo-remove", user, usePubKey)
assert.Error(t, err)
out, err := runSSHCommand(fmt.Sprintf("sftpgo-remove %v", testFileName), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
_, err := client.Stat(testFileName)
assert.Error(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)
}
out, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath1, testDir)), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
_, err := client.Stat(path.Join(vdirPath1, testFileName))
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, testFileSize1, user.UsedQuotaSize)
}
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", vdirPath1), user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand("sftpgo-remove /", user, usePubKey)
assert.Error(t, err)
_, err = runSSHCommand("sftpgo-remove missing_file", user, usePubKey)
assert.Error(t, err)
if runtime.GOOS != osWindows {
err = os.Chmod(filepath.Join(mappedPath2, testDir), 0555)
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath2, testDir)), user, usePubKey)
assert.Error(t, err)
err = os.Chmod(filepath.Join(mappedPath2, testDir), 0001)
assert.NoError(t, err)
_, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath2, testDir)), user, usePubKey)
assert.Error(t, err)
err = os.Chmod(filepath.Join(mappedPath2, testDir), os.ModePerm)
assert.NoError(t, err)
}
}
// test remove dir with no delete perm
user.Permissions["/"] = []string{dataprovider.PermUpload, dataprovider.PermDownload, dataprovider.PermListItems}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
conn, client, err = getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
_, err = runSSHCommand("sftpgo-remove adir", user, usePubKey)
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 = 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 TestSSHRemoveCryptFs(t *testing.T) {
usePubKey := false
u := getTestUserWithCryptFs(usePubKey)
u.QuotaFiles = 100
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
folderName1 := filepath.Base(mappedPath1)
vdirPath1 := "/vdir1/sub"
mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
folderName2 := filepath.Base(mappedPath2)
vdirPath2 := "/vdir2/sub"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName1,
},
VirtualPath: vdirPath1,
QuotaFiles: -1,
QuotaSize: -1,
})
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
},
VirtualPath: vdirPath2,
QuotaFiles: 100,
QuotaSize: 0,
})
f1 := vfs.BaseVirtualFolder{
Name: folderName1,
MappedPath: mappedPath1,
}
_, _, err := httpdtest.AddFolder(f1, http.StatusCreated)
assert.NoError(t, err)
f2 := vfs.BaseVirtualFolder{
Name: folderName2,
MappedPath: mappedPath2,
FsConfig: vfs.Filesystem{
Provider: sdk.CryptedFilesystemProvider,
CryptConfig: vfs.CryptFsConfig{
Passphrase: kms.NewPlainSecret(defaultPassword),
},
},
}
_, _, err = httpdtest.AddFolder(f2, http.StatusCreated)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
testDir := "tdir"
err = client.Mkdir(testDir)
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)
testFileSize := int64(32768)
testFileSize1 := int64(65536)
testFileName1 := "test_file1.dat"
err = writeSFTPFile(testFileName, testFileSize, client)
assert.NoError(t, err)
err = writeSFTPFile(testFileName1, testFileSize1, client)
assert.NoError(t, err)
err = writeSFTPFile(path.Join(testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = writeSFTPFile(path.Join(testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = writeSFTPFile(path.Join(vdirPath1, testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = writeSFTPFile(path.Join(vdirPath1, testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
err = writeSFTPFile(path.Join(vdirPath2, testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
err = writeSFTPFile(path.Join(vdirPath2, testDir, testFileName1), testFileSize1, client)
assert.NoError(t, err)
_, err = runSSHCommand("sftpgo-remove /vdir2", user, usePubKey)
assert.Error(t, err)
out, err := runSSHCommand(fmt.Sprintf("sftpgo-remove %v", testFileName), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
_, err := client.Stat(testFileName)
assert.Error(t, err)
}
out, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", testDir), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
}
out, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath1, testDir)), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
}
out, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath2, testDir, testFileName)), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
}
err = writeSFTPFile(path.Join(vdirPath2, testDir, testFileName), testFileSize, client)
assert.NoError(t, err)
out, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath2, testDir)), user, usePubKey)
if assert.NoError(t, err) {
assert.Equal(t, "OK\n", string(out))
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 1, user.UsedQuotaFiles)
assert.Greater(t, user.UsedQuotaSize, testFileSize1)
}
_, 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 TestBasicGitCommands(t *testing.T) {
if len(gitPath) == 0 || len(sshPath) == 0 || runtime.GOOS == osWindows {
t.Skip("git and/or ssh command not found or OS is windows, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
repoName := "testrepo" //nolint:goconst
clonePath := filepath.Join(homeBasePath, repoName)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(filepath.Join(homeBasePath, repoName))
assert.NoError(t, err)
out, err := initGitRepo(filepath.Join(user.HomeDir, repoName))
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = cloneGitRepo(homeBasePath, "/"+repoName, user.Username)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = addFileToGitRepo(clonePath, 128)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
user.QuotaFiles = 100000
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
out, err = pushToGitRepo(clonePath)
if !assert.NoError(t, err, "unexpected error, out: %v", string(out)) {
printLatestLogs(10)
}
out, err = addFileToGitRepo(clonePath, 131072)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
user.QuotaSize = user.UsedQuotaSize + 1
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
out, err = pushToGitRepo(clonePath)
assert.Error(t, err, "git push must fail if quota is exceeded, out: %v", string(out))
aDir := filepath.Join(user.GetHomeDir(), repoName, "adir")
err = os.MkdirAll(aDir, 0001)
assert.NoError(t, err)
_, err = pushToGitRepo(clonePath)
assert.Error(t, err)
err = os.Chmod(aDir, 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)
err = os.RemoveAll(clonePath)
assert.NoError(t, err)
}
func TestGitIncludedVirtualFolders(t *testing.T) {
if len(gitPath) == 0 || len(sshPath) == 0 || runtime.GOOS == osWindows {
t.Skip("git and/or ssh command not found or OS is windows, unable to execute this test")
}
usePubKey := true
repoName := "trepo"
u := getTestUser(usePubKey)
u.QuotaFiles = 10000
mappedPath := filepath.Join(os.TempDir(), "repo")
folderName := filepath.Base(mappedPath)
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: "/" + repoName,
QuotaFiles: -1,
QuotaSize: -1,
})
f := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
clonePath := filepath.Join(homeBasePath, repoName)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(filepath.Join(homeBasePath, repoName))
assert.NoError(t, err)
out, err := initGitRepo(mappedPath)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = cloneGitRepo(homeBasePath, "/"+repoName, user.Username)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = addFileToGitRepo(clonePath, 128)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = pushToGitRepo(clonePath)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
if user.UsedQuotaFiles == 0 {
assert.Eventually(t, func() bool {
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
if err != nil {
return false
}
return user.QuotaFiles > 0
}, 1*time.Second, 100*time.Millisecond)
}
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.UsedQuotaFiles, 0)
assert.Greater(t, user.UsedQuotaSize, int64(0))
folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, user.UsedQuotaFiles, folder.UsedQuotaFiles)
assert.Equal(t, user.UsedQuotaSize, folder.UsedQuotaSize)
_, 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)
err = os.RemoveAll(clonePath)
assert.NoError(t, err)
}
func TestGitQuotaVirtualFolders(t *testing.T) {
if len(gitPath) == 0 || len(sshPath) == 0 || runtime.GOOS == osWindows {
t.Skip("git and/or ssh command not found or OS is windows, unable to execute this test")
}
usePubKey := true
repoName := "testrepo"
u := getTestUser(usePubKey)
u.QuotaFiles = 1
u.QuotaSize = 131072
mappedPath := filepath.Join(os.TempDir(), "repo")
folderName := filepath.Base(mappedPath)
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: "/" + repoName,
QuotaFiles: 0,
QuotaSize: 0,
})
f := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
// we upload a file so the user is over quota
defer conn.Close()
defer client.Close()
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, u.QuotaSize)
assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, u.QuotaSize, client)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
clonePath := filepath.Join(homeBasePath, repoName)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(filepath.Join(homeBasePath, repoName))
assert.NoError(t, err)
out, err := initGitRepo(mappedPath)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = cloneGitRepo(homeBasePath, "/"+repoName, user.Username)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = addFileToGitRepo(clonePath, 128)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
out, err = pushToGitRepo(clonePath)
assert.NoError(t, err, "unexpected error, out: %v", string(out))
_, 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)
err = os.RemoveAll(clonePath)
assert.NoError(t, err)
}
func TestGitErrors(t *testing.T) {
if len(gitPath) == 0 || len(sshPath) == 0 || runtime.GOOS == osWindows {
t.Skip("git and/or ssh command not found or OS is windows, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
repoName := "testrepo"
clonePath := filepath.Join(homeBasePath, repoName)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(filepath.Join(homeBasePath, repoName))
assert.NoError(t, err)
out, err := cloneGitRepo(homeBasePath, "/"+repoName, user.Username)
assert.Error(t, err, "cloning a missing repo must fail, out: %v", string(out))
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(clonePath)
assert.NoError(t, err)
}
// Start SCP tests
func TestSCPBasicHandling(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
u.QuotaSize = 6553600
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.QuotaSize = 6553600
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(131074)
expectedQuotaSize := testFileSize
expectedQuotaFiles := 1
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
localPath := filepath.Join(homeBasePath, "scp_download.dat")
// test to download a missing file
err = scpDownload(localPath, remoteDownPath, false, false)
assert.Error(t, err, "downloading a missing file via scp must fail")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, int64(0), user.FirstUpload)
assert.Equal(t, int64(0), user.FirstDownload)
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.FirstUpload, int64(0))
assert.Equal(t, int64(0), user.FirstDownload)
err = scpDownload(localPath, remoteDownPath, false, false)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.FirstUpload, int64(0))
assert.Greater(t, user.FirstDownload, int64(0))
fi, err := os.Stat(localPath)
if assert.NoError(t, err) {
assert.Equal(t, testFileSize, fi.Size())
}
err = os.Remove(localPath)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
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.Remove(testFilePath)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
}
func TestSCPUploadFileOverwrite(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
u.QuotaFiles = 1000
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser(usePubKey)
u.QuotaFiles = 1000
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(32760)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.NoError(t, err)
// test a new upload that must overwrite the existing file
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, testFileSize, user.UsedQuotaSize)
assert.Equal(t, 1, user.UsedQuotaFiles)
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
localPath := filepath.Join(homeBasePath, "scp_download.dat")
err = scpDownload(localPath, remoteDownPath, false, false)
assert.NoError(t, err)
fi, err := os.Stat(localPath)
if assert.NoError(t, err) {
assert.Equal(t, testFileSize, fi.Size())
}
// now create a simlink via SFTP, replace the symlink with a file via SCP and check quota usage
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
err = client.Symlink(testFileName, testFileName+".link")
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, testFileSize, user.UsedQuotaSize)
assert.Equal(t, 1, user.UsedQuotaFiles)
}
err = scpUpload(testFilePath, remoteUpPath+".link", true, false)
assert.NoError(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, testFileSize*2, user.UsedQuotaSize)
assert.Equal(t, 2, user.UsedQuotaFiles)
err = os.Remove(localPath)
assert.NoError(t, err)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
user.ID = 0
user.CreatedAt = 0
_, resp, err := httpdtest.AddUser(user, http.StatusCreated)
assert.NoError(t, err, string(resp))
}
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPRecursive(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
localUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
testBaseDirName := "test_dir"
testBaseDirPath := filepath.Join(homeBasePath, testBaseDirName)
testBaseDirDownName := "test_dir_down" //nolint:goconst
testBaseDirDownPath := filepath.Join(homeBasePath, testBaseDirDownName)
testFilePath := filepath.Join(homeBasePath, testBaseDirName, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testBaseDirName, testBaseDirName, testFileName)
testFileSize := int64(131074)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize)
assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testBaseDirName))
// test to download a missing dir
err = scpDownload(testBaseDirDownPath, remoteDownPath, true, true)
assert.Error(t, err, "downloading a missing dir via scp must fail")
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
err = scpUpload(testBaseDirPath, remoteUpPath, true, false)
assert.NoError(t, err)
// overwrite existing dir
err = scpUpload(testBaseDirPath, remoteUpPath, true, false)
assert.NoError(t, err)
err = scpDownload(testBaseDirDownPath, remoteDownPath, true, true)
assert.NoError(t, err)
// test download without passing -r
err = scpDownload(testBaseDirDownPath, remoteDownPath, true, false)
assert.Error(t, err, "recursive download without -r must fail")
fi, err := os.Stat(filepath.Join(testBaseDirDownPath, testFileName))
if assert.NoError(t, err) {
assert.Equal(t, testFileSize, fi.Size())
}
fi, err = os.Stat(filepath.Join(testBaseDirDownPath, testBaseDirName, testFileName))
if assert.NoError(t, err) {
assert.Equal(t, testFileSize, fi.Size())
}
// upload to a non existent dir
remoteUpPath = fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/non_existent_dir")
err = scpUpload(testBaseDirPath, remoteUpPath, true, false)
assert.Error(t, err, "uploading via scp to a non existent dir must fail")
err = os.RemoveAll(testBaseDirDownPath)
assert.NoError(t, err)
if user.Username == defaultUsername {
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
user.Password = defaultPassword
user.ID = 0
user.CreatedAt = 0
_, resp, err := httpdtest.AddUser(user, http.StatusCreated)
assert.NoError(t, err, string(resp))
}
}
err = os.RemoveAll(testBaseDirPath)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPStartDirectory(t *testing.T) {
usePubKey := true
startDir := "/sta rt/dir"
u := getTestUser(usePubKey)
u.Filters.StartDirectory = startDir
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
localPath := filepath.Join(homeBasePath, "scp_download.dat")
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:", user.Username)
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
err = scpDownload(localPath, remoteDownPath, false, false)
assert.NoError(t, err)
// check that the file is in the start directory
_, err = os.Stat(filepath.Join(user.HomeDir, startDir, testFileName))
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localPath)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSCPPatternsFilter(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(131072)
testFilePath := filepath.Join(homeBasePath, testFileName)
localPath := filepath.Join(homeBasePath, "scp_download.dat")
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
user.Filters.FilePatterns = []sdk.PatternsFilter{
{
Path: "/",
AllowedPatterns: []string{"*.zip"},
DeniedPatterns: []string{},
},
}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
err = scpDownload(localPath, remoteDownPath, false, false)
assert.Error(t, err, "scp download must fail")
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.Error(t, err, "scp upload must fail")
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
_, err = os.Stat(localPath)
if err == nil {
err = os.Remove(localPath)
assert.NoError(t, err)
}
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSCPTransferQuotaLimits(t *testing.T) {
usePubKey := true
u := getTestUser(usePubKey)
u.DownloadDataTransfer = 1
u.UploadDataTransfer = 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(550000)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
err = scpDownload(localDownloadPath, remoteDownPath, false, false)
assert.NoError(t, err)
// error while download is active
err = scpDownload(localDownloadPath, remoteDownPath, false, false)
assert.Error(t, err)
// error before starting the download
err = scpDownload(localDownloadPath, remoteDownPath, false, false)
assert.Error(t, err)
// error while upload is active
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.Error(t, err)
// error before starting the upload
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.Error(t, err)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Greater(t, user.UsedDownloadDataTransfer, int64(1024*1024))
if !assert.Greater(t, user.UsedUploadDataTransfer, int64(1024*1024), user.UsedDownloadDataTransfer) {
printLatestLogs(30)
}
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSCPUploadMaxSize(t *testing.T) {
testFileSize := int64(65535)
usePubKey := true
u := getTestUser(usePubKey)
u.Filters.MaxUploadFileSize = testFileSize + 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
testFileSize1 := int64(131072)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
err = scpUpload(testFilePath1, remoteUpPath, false, false)
assert.Error(t, err)
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestSCPVirtualFolders(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
mappedPath := filepath.Join(os.TempDir(), "vdir")
folderName := filepath.Base(mappedPath)
vdirPath := "/vdir"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
},
VirtualPath: vdirPath,
})
f := vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
}
_, _, err := httpdtest.AddFolder(f, http.StatusCreated)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath, os.ModePerm)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testBaseDirName := "test_dir"
testBaseDirPath := filepath.Join(homeBasePath, testBaseDirName)
testBaseDirDownName := "test_dir_down"
testBaseDirDownPath := filepath.Join(homeBasePath, testBaseDirDownName)
testFilePath := filepath.Join(homeBasePath, testBaseDirName, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testBaseDirName, testBaseDirName, testFileName)
testFileSize := int64(131074)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize)
assert.NoError(t, err)
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, vdirPath)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, vdirPath)
err = scpUpload(testBaseDirPath, remoteUpPath, true, false)
assert.NoError(t, err)
err = scpDownload(testBaseDirDownPath, remoteDownPath, true, 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(testBaseDirPath)
assert.NoError(t, err)
err = os.RemoveAll(testBaseDirDownPath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
}
func TestSCPNestedFolders(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
baseUser, resp, err := httpdtest.AddUser(getTestUser(false), http.StatusCreated)
assert.NoError(t, err, string(resp))
usePubKey := true
u := getTestUser(usePubKey)
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))
baseDirDownPath := filepath.Join(os.TempDir(), "basedir-down")
err = os.Mkdir(baseDirDownPath, os.ModePerm)
assert.NoError(t, err)
baseDir := filepath.Join(os.TempDir(), "basedir")
err = os.Mkdir(baseDir, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(filepath.Join(baseDir, vdirSFTPPath), os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(filepath.Join(baseDir, vdirCryptPath), os.ModePerm)
assert.NoError(t, err)
err = createTestFile(filepath.Join(baseDir, vdirSFTPPath, testFileName), 32768)
assert.NoError(t, err)
err = createTestFile(filepath.Join(baseDir, vdirCryptPath, testFileName), 65535)
assert.NoError(t, err)
err = createTestFile(filepath.Join(baseDir, "vdir", testFileName), 65536)
assert.NoError(t, err)
remoteRootPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
err = scpUpload(filepath.Join(baseDir, "vdir"), remoteRootPath, true, false)
assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
info, err := client.Stat(path.Join(vdirCryptPath, testFileName))
assert.NoError(t, err)
assert.Equal(t, int64(65535), info.Size())
info, err = client.Stat(path.Join(vdirSFTPPath, testFileName))
assert.NoError(t, err)
assert.Equal(t, int64(32768), info.Size())
info, err = client.Stat(path.Join("/vdir", testFileName))
assert.NoError(t, err)
assert.Equal(t, int64(65536), info.Size())
}
err = scpDownload(baseDirDownPath, remoteRootPath, true, true)
assert.NoError(t, err)
assert.FileExists(t, filepath.Join(baseDirDownPath, user.Username, "vdir", testFileName))
assert.FileExists(t, filepath.Join(baseDirDownPath, user.Username, vdirCryptPath, testFileName))
assert.FileExists(t, filepath.Join(baseDirDownPath, user.Username, vdirSFTPPath, testFileName))
if runtime.GOOS != osWindows {
err = os.Chmod(filepath.Join(baseUser.GetHomeDir(), testFileName), 0001)
assert.NoError(t, err)
err = scpDownload(baseDirDownPath, remoteRootPath, true, true)
assert.Error(t, err)
err = os.Chmod(filepath.Join(baseUser.GetHomeDir(), testFileName), os.ModePerm)
assert.NoError(t, err)
}
// now change the password for the base user, so SFTP folder will not work
baseUser.Password = defaultPassword + "_mod"
_, _, err = httpdtest.UpdateUser(baseUser, http.StatusOK, "1")
assert.NoError(t, err)
err = scpUpload(filepath.Join(baseDir, "vdir"), remoteRootPath, true, false)
assert.Error(t, err)
err = scpDownload(baseDirDownPath, remoteRootPath, true, true)
assert.Error(t, err)
_, 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)
err = os.RemoveAll(baseDir)
assert.NoError(t, err)
err = os.RemoveAll(baseDirDownPath)
assert.NoError(t, err)
}
func TestSCPVirtualFoldersQuota(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
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"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName1,
},
VirtualPath: vdirPath1,
QuotaFiles: -1,
QuotaSize: -1,
})
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName2,
},
VirtualPath: vdirPath2,
QuotaFiles: 0,
QuotaSize: 0,
})
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)
err = os.MkdirAll(mappedPath1, os.ModePerm)
assert.NoError(t, err)
err = os.MkdirAll(mappedPath2, os.ModePerm)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testBaseDirName := "test_dir"
testBaseDirPath := filepath.Join(homeBasePath, testBaseDirName)
testBaseDirDownName := "test_dir_down"
testBaseDirDownPath := filepath.Join(homeBasePath, testBaseDirDownName)
testFilePath := filepath.Join(homeBasePath, testBaseDirName, testFileName)
testFilePath1 := filepath.Join(homeBasePath, testBaseDirName, testBaseDirName, testFileName)
testFileSize := int64(131074)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize)
assert.NoError(t, err)
remoteDownPath1 := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", vdirPath1))
remoteUpPath1 := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, vdirPath1)
remoteDownPath2 := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", vdirPath2))
remoteUpPath2 := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, vdirPath2)
// we upload two times to test overwrite
err = scpUpload(testBaseDirPath, remoteUpPath1, true, false)
assert.NoError(t, err)
err = scpDownload(testBaseDirDownPath, remoteDownPath1, true, true)
assert.NoError(t, err)
err = scpUpload(testBaseDirPath, remoteUpPath1, true, false)
assert.NoError(t, err)
err = scpDownload(testBaseDirDownPath, remoteDownPath1, true, true)
assert.NoError(t, err)
err = scpUpload(testBaseDirPath, remoteUpPath2, true, false)
assert.NoError(t, err)
err = scpDownload(testBaseDirDownPath, remoteDownPath2, true, true)
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, expectedQuotaSize, f.UsedQuotaSize)
assert.Equal(t, expectedQuotaFiles, f.UsedQuotaFiles)
f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, expectedQuotaSize, f.UsedQuotaSize)
assert.Equal(t, expectedQuotaFiles, 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(testBaseDirPath)
assert.NoError(t, err)
err = os.RemoveAll(testBaseDirDownPath)
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 TestSCPPermsSubDirs(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermAny}
u.Permissions["/somedir"] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
localPath := filepath.Join(homeBasePath, "scp_download.dat")
subPath := filepath.Join(user.GetHomeDir(), "somedir")
testFileSize := int64(65535)
err = os.MkdirAll(subPath, os.ModePerm)
assert.NoError(t, err)
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/somedir")
err = scpDownload(localPath, remoteDownPath, false, true)
assert.Error(t, err, "download a dir with no permissions must fail")
err = os.Remove(subPath)
assert.NoError(t, err)
err = createTestFile(subPath, testFileSize)
assert.NoError(t, err)
err = scpDownload(localPath, remoteDownPath, false, false)
assert.NoError(t, err)
if runtime.GOOS != osWindows {
err = os.Chmod(subPath, 0001)
assert.NoError(t, err)
err = scpDownload(localPath, remoteDownPath, false, false)
assert.Error(t, err, "download a file with no system permissions must fail")
err = os.Chmod(subPath, os.ModePerm)
assert.NoError(t, err)
}
err = os.Remove(localPath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPPermCreateDirs(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(32760)
testBaseDirName := "test_dir"
testBaseDirPath := filepath.Join(homeBasePath, testBaseDirName)
testFilePath1 := filepath.Join(homeBasePath, testBaseDirName, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
err = createTestFile(testFilePath1, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/tmp/")
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.Error(t, err, "scp upload must fail, the user cannot create files in a missing dir")
err = scpUpload(testBaseDirPath, remoteUpPath, true, false)
assert.Error(t, err, "scp upload must fail, the user cannot create new dirs")
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.RemoveAll(testBaseDirPath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPPermUpload(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermCreateDirs}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65536)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/tmp")
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.Error(t, err, "scp upload must fail, the user cannot upload")
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPPermOverwrite(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermUpload, dataprovider.PermCreateDirs}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65536)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/tmp")
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.NoError(t, err)
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.Error(t, err, "scp upload must fail, the user cannot ovewrite existing files")
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPPermDownload(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
u := getTestUser(usePubKey)
u.Permissions["/"] = []string{dataprovider.PermUpload, dataprovider.PermCreateDirs}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65537)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.NoError(t, err)
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
localPath := filepath.Join(homeBasePath, "scp_download.dat")
err = scpDownload(localPath, remoteDownPath, false, false)
assert.Error(t, err, "scp download must fail, the user cannot download")
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPQuotaSize(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
testFileSize := int64(65535)
u := getTestUser(usePubKey)
u.QuotaFiles = 1
u.QuotaSize = testFileSize + 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
testFileSize1 := int64(131072)
testFileName1 := "test_file1.dat"
testFilePath1 := filepath.Join(homeBasePath, testFileName1)
err = createTestFile(testFilePath1, testFileSize1)
assert.NoError(t, err)
testFileSize2 := int64(32768)
testFileName2 := "test_file2.dat"
testFilePath2 := filepath.Join(homeBasePath, testFileName2)
err = createTestFile(testFilePath2, testFileSize2)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.NoError(t, err)
err = scpUpload(testFilePath, remoteUpPath+".quota", true, false)
assert.Error(t, err, "user is over quota scp upload must fail")
// now test quota limits while uploading the current file, we have 1 bytes remaining
user.QuotaSize = testFileSize + 1
user.QuotaFiles = 0
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
err = scpUpload(testFilePath2, remoteUpPath+".quota", true, false)
assert.Error(t, err, "user is over quota scp upload must fail")
// overwriting an existing file will work if the resulting size is lesser or equal than the current one
err = scpUpload(testFilePath1, remoteUpPath, true, false)
assert.Error(t, err)
err = scpUpload(testFilePath2, remoteUpPath, true, false)
assert.NoError(t, err)
err = scpUpload(testFilePath, remoteUpPath, true, false)
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(testFilePath1)
assert.NoError(t, err)
err = os.Remove(testFilePath2)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPEscapeHomeDir(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
err = os.MkdirAll(user.GetHomeDir(), os.ModePerm)
assert.NoError(t, err)
testDir := "testDir"
linkPath := filepath.Join(homeBasePath, defaultUsername, testDir)
err = os.Symlink(homeBasePath, linkPath)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join(testDir, testDir))
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.Error(t, err, "uploading to a dir with a symlink outside home dir must fail")
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testDir, testFileName))
localPath := filepath.Join(homeBasePath, "scp_download.dat")
err = scpDownload(localPath, remoteDownPath, false, false)
assert.Error(t, err, "scp download must fail, the requested file has a symlink outside user home")
remoteDownPath = fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testDir))
err = scpDownload(homeBasePath, remoteDownPath, false, true)
assert.Error(t, err, "scp download must fail, the requested dir is a symlink outside user home")
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPUploadPaths(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
testDirName := "testDir"
testDirPath := filepath.Join(user.GetHomeDir(), testDirName)
err = os.MkdirAll(testDirPath, os.ModePerm)
assert.NoError(t, err)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, testDirName)
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join(testDirName, testFileName))
localPath := filepath.Join(homeBasePath, "scp_download.dat")
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
err = scpDownload(localPath, remoteDownPath, false, false)
assert.NoError(t, err)
// upload a file to a missing dir
remoteUpPath = fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join(testDirName, testDirName, testFileName))
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.Error(t, err, "scp upload to a missing dir must fail")
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
err = os.Remove(localPath)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPOverwriteDirWithFile(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
testDirPath := filepath.Join(user.GetHomeDir(), testFileName)
err = os.MkdirAll(testDirPath, os.ModePerm)
assert.NoError(t, err)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.Error(t, err, "copying a file over an existing dir must fail")
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPRemoteToRemote(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
if runtime.GOOS == osWindows {
t.Skip("scp between remote hosts is not supported on Windows")
}
usePubKey := true
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
assert.NoError(t, err)
u := getTestUser(usePubKey)
u.Username += "1"
u.HomeDir += "1"
user1, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
remote1UpPath := fmt.Sprintf("%v@127.0.0.1:%v", user1.Username, path.Join("/", testFileName))
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
err = scpUpload(remoteUpPath, remote1UpPath, false, true)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user1.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user1, http.StatusOK)
assert.NoError(t, err)
}
func TestSCPErrors(t *testing.T) {
if scpPath == "" {
t.Skip("scp command not found, unable to execute this test")
}
u := getTestUser(true)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
testFileSize := int64(524288)
testFilePath := filepath.Join(homeBasePath, testFileName)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/")
remoteDownPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, path.Join("/", testFileName))
localPath := filepath.Join(homeBasePath, "scp_download.dat")
err = scpUpload(testFilePath, remoteUpPath, false, false)
assert.NoError(t, err)
user.UploadBandwidth = 512
user.DownloadBandwidth = 512
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
cmd := getScpDownloadCommand(localPath, remoteDownPath, false, false)
go func() {
err := cmd.Run()
assert.Error(t, err, "SCP download must fail")
}()
waitForActiveTransfers(t)
// wait some additional arbitrary time to wait for transfer activity to happen
// it is need to reach all the code in CheckIdleConnections
time.Sleep(100 * time.Millisecond)
err = cmd.Process.Kill()
assert.NoError(t, err)
assert.Eventually(t, func() bool { return len(common.Connections.GetStats("")) == 0 }, 2*time.Second, 100*time.Millisecond)
cmd = getScpUploadCommand(testFilePath, remoteUpPath, false, false)
go func() {
err := cmd.Run()
assert.Error(t, err, "SCP upload must fail")
}()
waitForActiveTransfers(t)
// wait some additional arbitrary time to wait for transfer activity to happen
// it is need to reach all the code in CheckIdleConnections
time.Sleep(100 * time.Millisecond)
err = cmd.Process.Kill()
assert.NoError(t, err)
assert.Eventually(t, func() bool { return len(common.Connections.GetStats("")) == 0 }, 2*time.Second, 100*time.Millisecond)
err = os.Remove(testFilePath)
assert.NoError(t, err)
os.Remove(localPath)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
}
// End SCP tests
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 getTestGroup() dataprovider.Group {
return dataprovider.Group{
BaseGroup: sdk.BaseGroup{
Name: "test_group",
Description: "test group description",
},
}
}
func getTestUser(usePubKey bool) 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
if usePubKey {
user.PublicKeys = []string{testPubKey}
user.Password = ""
}
return user
}
func getTestSFTPUser(usePubKey bool) dataprovider.User {
u := getTestUser(usePubKey)
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)
if usePubKey {
u.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(testPrivateKey)
u.FsConfig.SFTPConfig.Fingerprints = hostKeyFPs
}
return u
}
func runSSHCommand(command string, user dataprovider.User, usePubKey bool) ([]byte, error) {
var sshSession *ssh.Session
var output []byte
config := &ssh.ClientConfig{
User: user.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 5 * time.Second,
}
if usePubKey {
key, err := ssh.ParsePrivateKey([]byte(testPrivateKey))
if err != nil {
return output, err
}
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(key)}
} 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 getSignerForUserCert(certBytes []byte) (ssh.Signer, error) {
signer, err := ssh.ParsePrivateKey([]byte(testPrivateKey))
if err != nil {
return nil, err
}
cert, _, _, _, err := ssh.ParseAuthorizedKey(certBytes) //nolint:dogsled
if err != nil {
return nil, err
}
return ssh.NewCertSigner(cert.(*ssh.Certificate), signer)
}
func getSftpClientWithAddr(user dataprovider.User, usePubKey bool, addr string) (*ssh.Client, *sftp.Client, error) {
var sftpClient *sftp.Client
config := &ssh.ClientConfig{
User: user.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 5 * time.Second,
}
if usePubKey {
signer, err := ssh.ParsePrivateKey([]byte(testPrivateKey))
if err != nil {
return nil, nil, err
}
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
} else {
if user.Password != "" {
if user.Password == "empty" {
config.Auth = []ssh.AuthMethod{ssh.Password("")}
} else {
config.Auth = []ssh.AuthMethod{ssh.Password(user.Password)}
}
} else {
config.Auth = []ssh.AuthMethod{ssh.Password(defaultPassword)}
}
}
conn, err := ssh.Dial("tcp", addr, 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, usePubKey bool) (*ssh.Client, *sftp.Client, error) {
return getSftpClientWithAddr(user, usePubKey, sftpServerAddr)
}
func getKeyboardInteractiveSftpClient(user dataprovider.User, answers []string) (*ssh.Client, *sftp.Client, error) {
var sftpClient *sftp.Client
config := &ssh.ClientConfig{
User: user.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.KeyboardInteractive(func(_, _ string, _ []string, _ []bool) ([]string, error) {
return answers, nil
}),
},
Timeout: 5 * time.Second,
}
conn, err := ssh.Dial("tcp", sftpServerAddr, config)
if err != nil {
return nil, sftpClient, err
}
sftpClient, err = sftp.NewClient(conn)
if err != nil {
conn.Close()
}
return conn, sftpClient, err
}
func getCustomAuthSftpClient(user dataprovider.User, authMethods []ssh.AuthMethod, addr string) (*ssh.Client, *sftp.Client, error) {
var sftpClient *sftp.Client
config := &ssh.ClientConfig{
User: user.Username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: authMethods,
Timeout: 5 * time.Second,
}
var err error
var conn *ssh.Client
if addr != "" {
conn, err = ssh.Dial("tcp", addr, config)
} else {
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 createTestFile(path string, size int64) error {
baseDir := filepath.Dir(path)
if _, err := os.Stat(baseDir); errors.Is(err, fs.ErrNotExist) {
err = os.MkdirAll(baseDir, os.ModePerm)
if err != nil {
return err
}
}
content := make([]byte, size)
_, err := rand.Read(content)
if err != nil {
return err
}
return os.WriteFile(path, content, os.ModePerm)
}
func appendToTestFile(path string, size int64) error {
content := make([]byte, size)
_, err := rand.Read(content)
if err != nil {
return err
}
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModePerm)
if err != nil {
return err
}
defer f.Close()
written, err := io.Copy(f, bytes.NewReader(content))
if err != nil {
return err
}
if written != size {
return fmt.Errorf("write error, written: %v/%v", written, size)
}
return nil
}
func checkBasicSFTP(client *sftp.Client) error {
_, err := client.Getwd()
if err != nil {
return err
}
_, err = client.ReadDir(".")
return err
}
func writeSFTPFile(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
}
err = f.Close()
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 sftpUploadFile(localSourcePath string, remoteDestPath string, expectedSize int64, client *sftp.Client) error {
srcFile, err := os.Open(localSourcePath)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := client.Create(remoteDestPath)
if err != nil {
return err
}
_, err = io.Copy(destFile, srcFile)
if err != nil {
destFile.Close()
return err
}
// we need to close the file to trigger the server side close method
// we cannot defer closing otherwise Stat will fail for upload atomic mode
destFile.Close()
if expectedSize > 0 {
fi, err := client.Stat(remoteDestPath)
if err != nil {
return err
}
if fi.Size() != expectedSize {
return fmt.Errorf("uploaded file size does not match, actual: %v, expected: %v", fi.Size(), expectedSize)
}
}
return err
}
func sftpUploadResumeFile(localSourcePath string, remoteDestPath string, expectedSize int64, invalidOffset bool, //nolint:unparam
client *sftp.Client) error {
srcFile, err := os.Open(localSourcePath)
if err != nil {
return err
}
defer srcFile.Close()
fi, err := client.Lstat(remoteDestPath)
if err != nil {
return err
}
if !invalidOffset {
_, err = srcFile.Seek(fi.Size(), 0)
if err != nil {
return err
}
}
destFile, err := client.OpenFile(remoteDestPath, os.O_WRONLY|os.O_APPEND)
if err != nil {
return err
}
if !invalidOffset {
_, err = destFile.Seek(fi.Size(), 0)
if err != nil {
return err
}
}
_, err = io.Copy(destFile, srcFile)
if err != nil {
destFile.Close()
return err
}
// we need to close the file to trigger the server side close method
// we cannot defer closing otherwise Stat will fail for upload atomic mode
destFile.Close()
if expectedSize > 0 {
fi, err := client.Lstat(remoteDestPath)
if err != nil {
return err
}
if fi.Size() != expectedSize {
return fmt.Errorf("uploaded file size does not match, actual: %v, expected: %v", fi.Size(), expectedSize)
}
}
return err
}
func sftpDownloadFile(remoteSourcePath string, localDestPath string, expectedSize int64, client *sftp.Client) error {
downloadDest, err := os.Create(localDestPath)
if err != nil {
return err
}
defer downloadDest.Close()
sftpSrcFile, err := client.Open(remoteSourcePath)
if err != nil {
return err
}
defer sftpSrcFile.Close()
_, err = io.Copy(downloadDest, sftpSrcFile)
if err != nil {
return err
}
err = downloadDest.Sync()
if err != nil {
return err
}
if expectedSize > 0 {
fi, err := downloadDest.Stat()
if err != nil {
return err
}
if fi.Size() != expectedSize {
return fmt.Errorf("downloaded file size does not match, actual: %v, expected: %v", fi.Size(), expectedSize)
}
}
return err
}
func sftpUploadNonBlocking(localSourcePath string, remoteDestPath string, expectedSize int64, client *sftp.Client) <-chan error {
c := make(chan error, 1)
go func() {
c <- sftpUploadFile(localSourcePath, remoteDestPath, expectedSize, client)
}()
return c
}
func sftpDownloadNonBlocking(remoteSourcePath string, localDestPath string, expectedSize int64, client *sftp.Client) <-chan error {
c := make(chan error, 1)
go func() {
c <- sftpDownloadFile(remoteSourcePath, localDestPath, expectedSize, client)
}()
return c
}
func scpUpload(localPath, remotePath string, preserveTime, remoteToRemote bool) error {
cmd := getScpUploadCommand(localPath, remotePath, preserveTime, remoteToRemote)
return cmd.Run()
}
func scpDownload(localPath, remotePath string, preserveTime, recursive bool) error {
cmd := getScpDownloadCommand(localPath, remotePath, preserveTime, recursive)
return cmd.Run()
}
func getScpDownloadCommand(localPath, remotePath string, preserveTime, recursive bool) *exec.Cmd {
var args []string
if preserveTime {
args = append(args, "-p")
}
if recursive {
args = append(args, "-r")
}
if scpForce {
args = append(args, "-O")
}
args = append(args, "-P")
args = append(args, "2022")
args = append(args, "-o")
args = append(args, "StrictHostKeyChecking=no")
args = append(args, "-i")
args = append(args, privateKeyPath)
args = append(args, remotePath)
args = append(args, localPath)
return exec.Command(scpPath, args...)
}
func getScpUploadCommand(localPath, remotePath string, preserveTime, remoteToRemote bool) *exec.Cmd {
var args []string
if remoteToRemote {
args = append(args, "-3")
}
if preserveTime {
args = append(args, "-p")
}
fi, err := os.Stat(localPath)
if err == nil {
if fi.IsDir() {
args = append(args, "-r")
}
}
if scpForce {
args = append(args, "-O")
}
args = append(args, "-P")
args = append(args, "2022")
args = append(args, "-o")
args = append(args, "StrictHostKeyChecking=no")
args = append(args, "-o")
args = append(args, "HostKeyAlgorithms=+ssh-rsa")
args = append(args, "-i")
args = append(args, privateKeyPath)
args = append(args, localPath)
args = append(args, remotePath)
return exec.Command(scpPath, args...)
}
func computeHashForFile(hasher hash.Hash, path string) (string, error) {
hash := ""
f, err := os.Open(path)
if err != nil {
return hash, err
}
defer f.Close()
_, err = io.Copy(hasher, f)
if err == nil {
hash = fmt.Sprintf("%x", hasher.Sum(nil))
}
return hash, err
}
func waitForActiveTransfers(t *testing.T) {
assert.Eventually(t, func() bool {
for _, stat := range common.Connections.GetStats("") {
if len(stat.Transfers) > 0 {
return true
}
}
return false
}, 1*time.Second, 50*time.Millisecond)
}
func checkSystemCommands() {
var err error
gitPath, err = exec.LookPath("git")
if err != nil {
logger.Warn(logSender, "", "unable to get git command. GIT tests will be skipped, err: %v", err)
logger.WarnToConsole("unable to get git command. GIT tests will be skipped, err: %v", err)
gitPath = ""
}
sshPath, err = exec.LookPath("ssh")
if err != nil {
logger.Warn(logSender, "", "unable to get ssh command. GIT tests will be skipped, err: %v", err)
logger.WarnToConsole("unable to get ssh command. GIT tests will be skipped, err: %v", err)
gitPath = ""
}
hookCmdPath, err = exec.LookPath("true")
if err != nil {
logger.Warn(logSender, "", "unable to get hook command: %v", err)
logger.WarnToConsole("unable to get hook command: %v", err)
}
scpPath, err = exec.LookPath("scp")
if err != nil {
logger.Warn(logSender, "", "unable to get scp command. SCP tests will be skipped, err: %v", err)
logger.WarnToConsole("unable to get scp command. SCP tests will be skipped, err: %v", err)
scpPath = ""
} else {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, scpPath, "-O")
out, _ := cmd.CombinedOutput()
scpForce = !strings.Contains(string(out), "option -- O")
}
}
func initGitRepo(path string) ([]byte, error) {
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return nil, err
}
args := []string{"init", "--bare"}
cmd := exec.Command(gitPath, args...)
cmd.Dir = path
return cmd.CombinedOutput()
}
func pushToGitRepo(repoPath string) ([]byte, error) {
cmd := exec.Command(gitPath, "push")
cmd.Dir = repoPath
cmd.Env = append(os.Environ(),
fmt.Sprintf("GIT_SSH=%v", gitWrapPath))
return cmd.CombinedOutput()
}
func cloneGitRepo(basePath, remotePath, username string) ([]byte, error) {
remoteURL := fmt.Sprintf("ssh://%v@127.0.0.1:2022%v", username, remotePath)
args := []string{"clone", remoteURL}
cmd := exec.Command(gitPath, args...)
cmd.Dir = basePath
cmd.Env = append(os.Environ(),
fmt.Sprintf("GIT_SSH=%v", gitWrapPath))
return cmd.CombinedOutput()
}
func addFileToGitRepo(repoPath string, fileSize int64) ([]byte, error) {
path := filepath.Join(repoPath, "test")
err := createTestFile(path, fileSize)
if err != nil {
return []byte(""), err
}
cmd := exec.Command(gitPath, "config", "user.email", "testuser@example.com")
cmd.Dir = repoPath
out, err := cmd.CombinedOutput()
if err != nil {
return out, err
}
cmd = exec.Command(gitPath, "config", "user.name", "testuser")
cmd.Dir = repoPath
out, err = cmd.CombinedOutput()
if err != nil {
return out, err
}
cmd = exec.Command(gitPath, "add", "test")
cmd.Dir = repoPath
out, err = cmd.CombinedOutput()
if err != nil {
return out, err
}
cmd = exec.Command(gitPath, "commit", "-am", "test")
cmd.Dir = repoPath
return cmd.CombinedOutput()
}
func getKeyboardInteractiveScriptForBuiltinChecks(addPasscode bool, result int) []byte {
content := []byte("#!/bin/sh\n\n")
echos := []bool{false}
q, _ := json.Marshal([]string{"Password: "})
e, _ := json.Marshal(echos)
content = append(content, []byte(fmt.Sprintf("echo '{\"questions\":%v,\"echos\":%v,\"check_password\":1}'\n", string(q), string(e)))...)
content = append(content, []byte("read ANSWER\n\n")...)
content = append(content, []byte("if test \"$ANSWER\" != \"OK\"; then\n")...)
content = append(content, []byte("exit 1\n")...)
content = append(content, []byte("fi\n\n")...)
if addPasscode {
q, _ := json.Marshal([]string{"Passcode: "})
content = append(content, []byte(fmt.Sprintf("echo '{\"questions\":%v,\"echos\":%v,\"check_password\":2}'\n", string(q), string(e)))...)
content = append(content, []byte("read ANSWER\n\n")...)
content = append(content, []byte("if test \"$ANSWER\" != \"OK\"; then\n")...)
content = append(content, []byte("exit 1\n")...)
content = append(content, []byte("fi\n\n")...)
}
content = append(content, []byte(fmt.Sprintf("echo '{\"auth_result\":%v}'\n", result))...)
return content
}
func getKeyboardInteractiveScriptContent(questions []string, sleepTime int, nonJSONResponse bool, result int) []byte {
content := []byte("#!/bin/sh\n\n")
q, _ := json.Marshal(questions)
echos := []bool{}
for index := range questions {
echos = append(echos, index%2 == 0)
}
e, _ := json.Marshal(echos)
if nonJSONResponse {
content = append(content, []byte(fmt.Sprintf("echo 'questions: %v echos: %v\n", string(q), string(e)))...)
} else {
content = append(content, []byte(fmt.Sprintf("echo '{\"questions\":%v,\"echos\":%v}'\n", string(q), string(e)))...)
}
for index := range questions {
content = append(content, []byte(fmt.Sprintf("read ANSWER%v\n", index))...)
}
if sleepTime > 0 {
content = append(content, []byte(fmt.Sprintf("sleep %v\n", sleepTime))...)
}
content = append(content, []byte(fmt.Sprintf("echo '{\"auth_result\":%v}'\n", result))...)
return content
}
func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse, emptyResponse bool, username string) []byte {
extAuthContent := []byte("#!/bin/sh\n\n")
if emptyResponse {
return extAuthContent
}
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...)
if username != "" {
user.Username = username
}
u, _ := json.Marshal(user)
if nonJSONResponse {
extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
} else {
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
}
extAuthContent = append(extAuthContent, []byte("else\n")...)
if nonJSONResponse {
extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
} else {
extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...)
}
extAuthContent = append(extAuthContent, []byte("fi\n")...)
return extAuthContent
}
func getPreLoginScriptContent(user dataprovider.User, nonJSONResponse bool) []byte {
content := []byte("#!/bin/sh\n\n")
if nonJSONResponse {
content = append(content, []byte("echo 'text response'\n")...)
return content
}
if len(user.Username) > 0 {
u, _ := json.Marshal(user)
content = append(content, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
}
return content
}
func getExitCodeScriptContent(exitCode int) []byte {
content := []byte("#!/bin/sh\n\n")
content = append(content, []byte(fmt.Sprintf("exit %v", exitCode))...)
return content
}
func getCheckPwdScriptsContents(status int, toVerify string) []byte {
content := []byte("#!/bin/sh\n\n")
content = append(content, []byte(fmt.Sprintf("echo '{\"status\":%v,\"to_verify\":\"%v\"}'\n", status, toVerify))...)
if status > 0 {
content = append(content, []byte("exit 0")...)
} else {
content = append(content, []byte("exit 1")...)
}
return content
}
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(line)
}
}
func getHostKeyFingerprint(name string) (string, error) {
privateBytes, err := os.ReadFile(name)
if err != nil {
return "", err
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return "", err
}
return ssh.FingerprintSHA256(private.PublicKey()), nil
}
func getHostKeysFingerprints(hostKeys []string) {
for _, k := range hostKeys {
fp, err := getHostKeyFingerprint(filepath.Join(configDir, k))
if err != nil {
logger.ErrorToConsole("unable to get fingerprint for host key %q: %v", k, err)
os.Exit(1)
}
hostKeyFPs = append(hostKeyFPs, fp)
}
}
func createInitialFiles(scriptArgs string) {
pubKeyPath = filepath.Join(homeBasePath, "ssh_key.pub")
privateKeyPath = filepath.Join(homeBasePath, "ssh_key")
trustedCAUserKey = filepath.Join(homeBasePath, "ca_user_key")
gitWrapPath = filepath.Join(homeBasePath, "gitwrap.sh")
extAuthPath = filepath.Join(homeBasePath, "extauth.sh")
preLoginPath = filepath.Join(homeBasePath, "prelogin.sh")
postConnectPath = filepath.Join(homeBasePath, "postconnect.sh")
checkPwdPath = filepath.Join(homeBasePath, "checkpwd.sh")
preDownloadPath = filepath.Join(homeBasePath, "predownload.sh")
preUploadPath = filepath.Join(homeBasePath, "preupload.sh")
revokeUserCerts = filepath.Join(homeBasePath, "revoked_certs.json")
err := os.WriteFile(pubKeyPath, []byte(testPubKey+"\n"), 0600)
if err != nil {
logger.WarnToConsole("unable to save public key to file: %v", err)
}
err = os.WriteFile(privateKeyPath, []byte(testPrivateKey+"\n"), 0600)
if err != nil {
logger.WarnToConsole("unable to save private key to file: %v", err)
}
err = os.WriteFile(gitWrapPath, []byte(fmt.Sprintf("%v -i %v -oStrictHostKeyChecking=no %v\n",
sshPath, privateKeyPath, scriptArgs)), os.ModePerm)
if err != nil {
logger.WarnToConsole("unable to save gitwrap shell script: %v", err)
}
err = os.WriteFile(trustedCAUserKey, []byte(testCAUserKey), 0600)
if err != nil {
logger.WarnToConsole("unable to save trusted CA user key: %v", err)
}
err = os.WriteFile(revokeUserCerts, []byte(`[]`), 0644)
if err != nil {
logger.WarnToConsole("unable to save revoked user certs: %v", err)
}
}