package sftpd_test import ( "bufio" "bytes" "crypto/rand" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/binary" "encoding/json" "fmt" "hash" "io" "io/ioutil" "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/lib/pq" _ "github.com/mattn/go-sqlite3" "github.com/pkg/sftp" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "golang.org/x/crypto/ssh" "github.com/drakkan/sftpgo/common" "github.com/drakkan/sftpgo/config" "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/httpdtest" "github.com/drakkan/sftpgo/kms" "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/sftpd" "github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/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-----` // 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" configDir = ".." osWindows = "windows" testFileName = "test_file_sftp.dat" testDLFileName = "test_download_sftp.dat" ) var ( allPerms = []string{dataprovider.PermAny} homeBasePath string scpPath string gitPath string sshPath string hookCmdPath string pubKeyPath string privateKeyPath string trustedCAUserKey string gitWrapPath string extAuthPath string keyIntAuthPath string preLoginPath string postConnectPath 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, 5, 1, 28, false, zerolog.DebugLevel) err := ioutil.WriteFile(loginBannerFile, []byte("simple login banner\n"), os.ModePerm) if err != nil { logger.ErrorToConsole("error creating login banner: %v", err) } 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() // we run the test cases with UploadMode atomic and resume support. The non atomic code path // simply does not execute some code so if it works in atomic mode will // work in non atomic mode too commonConf.UploadMode = 2 homeBasePath = os.TempDir() checkSystemCommands() var scriptArgs string if runtime.GOOS == osWindows { scriptArgs = "%*" } else { commonConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete", "ssh_cmd"} commonConf.Actions.Hook = hookCmdPath scriptArgs = "$@" } err = common.Initialize(commonConf) if err != nil { logger.WarnToConsole("error initializing common: %v", err) os.Exit(1) } err = dataprovider.Initialize(providerConf, configDir, true) if err != nil { logger.ErrorToConsole("error initializing data provider: %v", err) os.Exit(1) } httpConfig := config.GetHTTPConfig() httpConfig.Initialize(configDir) kmsConfig := config.GetKMSConfig() err = kmsConfig.Initialize() if err != nil { logger.ErrorToConsole("error initializing kms: %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", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384"} sftpdConf.Ciphers = []string{"chacha20-poly1305@openssh.com", "aes128-gcm@openssh.com", "aes256-ctr"} sftpdConf.MACs = []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256"} sftpdConf.LoginBannerFile = loginBannerFileName // we need to test all supported ssh commands sftpdConf.EnabledSSHCommands = []string{"*"} keyIntAuthPath = filepath.Join(homeBasePath, "keyintauth.sh") err = ioutil.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.KeyboardInteractiveHook = keyIntAuthPath createInitialFiles(scriptArgs) sftpdConf.TrustedUserCAKeys = append(sftpdConf.TrustedUserCAKeys, trustedCAUserKey) go func() { logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf) if err := sftpdConf.Initialize(configDir); err != nil { logger.ErrorToConsole("could not start SFTP server: %v", err) os.Exit(1) } }() go func() { if err := httpdConf.Initialize(configDir); 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() { 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 1: %v", err) os.Exit(1) } }() 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) exitCode := m.Run() os.Remove(logFilePath) os.Remove(loginBannerFile) os.Remove(pubKeyPath) os.Remove(privateKeyPath) os.Remove(trustedCAUserKey) os.Remove(gitWrapPath) os.Remove(extAuthPath) os.Remove(preLoginPath) os.Remove(postConnectPath) 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.KeyboardInteractiveHook = "invalid_file" err = sftpdConf.Initialize(configDir) assert.Error(t, err) sftpdConf.KeyboardInteractiveHook = filepath.Join(homeBasePath, "invalid_file") err = sftpdConf.Initialize(configDir) assert.Error(t, err) sftpdConf.Bindings = []sftpd.Binding{ { Port: 4444, ApplyProxyConfig: true, }, } common.Config.ProxyProtocol = 1 common.Config.ProxyAllowed = []string{"1270.0.0.1"} assert.True(t, sftpdConf.Bindings[0].HasProxy()) err = sftpdConf.Initialize(configDir) assert.Error(t, err) 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()) } 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) 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.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) 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) status := sftpd.GetStatus() assert.True(t, status.IsActive) sshCommands := status.GetSSHCommandsAsString() assert.NotEmpty(t, sshCommands) } 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 user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() err = checkBasicSFTP(client) assert.NoError(t, err) testFilePath := filepath.Join(homeBasePath, testFileName) testFileSize := int64(65535) testLinkName := testFileName + ".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) } 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) 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 TestLoginNonExistentUser(t *testing.T) { usePubKey := true user := getTestUser(usePubKey) _, err := getSftpClient(user, usePubKey) assert.Error(t, err) } func TestDefender(t *testing.T) { oldConfig := config.GetCommonConfig() cfg := config.GetCommonConfig() cfg.DefenderConfig.Enabled = true cfg.DefenderConfig.Threshold = 3 err := common.Initialize(cfg) assert.NoError(t, err) usePubKey := false user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() err = checkBasicSFTP(client) assert.NoError(t, err) } for i := 0; i < 3; i++ { user.Password = "wrong_pwd" _, err = getSftpClient(user, usePubKey) assert.Error(t, err) } user.Password = defaultPassword _, 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) 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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(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) { 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) closedConns := int32(0) for i := 0; i < numLogins; i++ { wg.Add(1) go func(counter int) { defer wg.Done() defer atomic.AddInt32(&closedConns, 1) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { err = checkBasicSFTP(client) 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() } }(i) } wg.Add(1) go func() { defer wg.Done() maxConns := 0 maxSessions := 0 for { servedReqs := atomic.LoadInt32(&closedConns) 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() 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() } 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) } 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) client, err := getSftpClientWithAddr(user, usePubKey, sftpSrvAddr2222) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } client, err = getSftpClientWithAddr(user, usePubKey, "127.0.0.1:2224") if !assert.Error(t, err) { client.Close() } _, err = httpdtest.RemoveUser(user, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(user.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) for _, user := range []dataprovider.User{localUser, sftpUser} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, "file upload resume 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(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 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 os.IsNotExist error _, err = client.Stat("missing path") assert.True(t, os.IsNotExist(err)) _, err = client.Lstat("missing path") assert.True(t, os.IsNotExist(err)) // 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(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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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(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) client, err := getSftpClient(sftpUser, usePubKey) if assert.NoError(t, err) { 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) client, err = getSftpClient(sftpUser, usePubKey) if assert.NoError(t, err) { 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) _, err = getSftpClient(sftpUser, usePubKey) assert.Error(t, err) _, err = runSSHCommand("md5sum", sftpUser, usePubKey) 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 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() testFilePath := filepath.Join(homeBasePath, testFileName) testFileSize := int64(65535) testDir := "test" 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(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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) if assert.Error(t, err) { assert.Contains(t, err.Error(), "SSH_FX_FAILURE") } 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) client, err := getSftpClient(localUser, usePubKey) if assert.NoError(t, err) { 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) } client, err = getSftpClient(sftpUser, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(localUser, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, false) if assert.NoError(t, err) { 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) } client, err = getSftpClient(user, true) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } user.Password = "invalid password" client, err = getSftpClient(user, false) if !assert.Error(t, err, "login with invalid password must fail") { client.Close() } // testPubKey1 is not authorized user.PublicKeys = []string{testPubKey1} user.Password = "" _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, true) if !assert.Error(t, err, "login with invalid public key must fail") { 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) client, err = getSftpClient(user, true) if assert.NoError(t, err) { 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) u.PublicKeys = []string{testCertValid, testCertUntrustedCA, testHostCert, testCertOtherSourceAddress, testCertExpired} 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) client, err := getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "") if assert.NoError(t, err) { 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) client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "") if !assert.Error(t, err) { client.Close() } // try login using an host certificate instead of an user certificate signer, err = getSignerForUserCert([]byte(testHostCert)) assert.NoError(t, err) client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "") if !assert.Error(t, err) { client.Close() } // try login using a user certificate with an authorized source address different from localhost signer, err = getSignerForUserCert([]byte(testCertOtherSourceAddress)) assert.NoError(t, err) client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "") if !assert.Error(t, err) { client.Close() } // try login using an expired certificate signer, err = getSignerForUserCert([]byte(testCertExpired)) assert.NoError(t, err) client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "") if !assert.Error(t, err) { client.Close() } _, err = httpdtest.RemoveUser(user, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) assert.NoError(t, err) // 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) client, err = getCustomAuthSftpClient(user, []ssh.AuthMethod{ssh.PublicKeys(signer)}, "") if !assert.Error(t, err) { client.Close() } _, 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) client, err := getSftpClient(user, true) if !assert.Error(t, err, "login with public key is disallowed and must fail") { client.Close() } client, err = getSftpClient(user, true) if !assert.Error(t, err, "login with password is disallowed and must fail") { client.Close() } signer, _ := ssh.ParsePrivateKey([]byte(testPrivateKey)) authMethods := []ssh.AuthMethod{ ssh.PublicKeys(signer), ssh.Password(defaultPassword), } client, err = getCustomAuthSftpClient(user, authMethods, "") if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } 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() } 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 = ioutil.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, 1), os.ModePerm) assert.NoError(t, err) client, err := getSftpClient(user, true) if !assert.Error(t, err, "login with public key is disallowed and must fail") { client.Close() } signer, _ := ssh.ParsePrivateKey([]byte(testPrivateKey)) authMethods := []ssh.AuthMethod{ ssh.PublicKeys(signer), ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) { return []string{"1", "2"}, nil }), } client, err = getCustomAuthSftpClient(user, authMethods, "") if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } client, err = getCustomAuthSftpClient(user, authMethods, sftpSrvAddr2222) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } authMethods = []ssh.AuthMethod{ ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []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) client, err = getCustomAuthSftpClient(user, authMethods, "") if assert.NoError(t, err) { assert.NoError(t, checkBasicSFTP(client)) client.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.PublicKeys = []string{testCertValid, testCertOtherSourceAddress} 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), } client, err := getCustomAuthSftpClient(user, authMethods, "") if assert.NoError(t, err) { 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), } client, err = getCustomAuthSftpClient(user, authMethods, "") if !assert.Error(t, err) { client.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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err, "login for a disabled user must fail") { client.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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 = utils.GetTimeAsMsSinceEpoch(time.Now()) - 120000 user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err, "login for an expired user must fail") { client.Close() } user.ExpirationDate = utils.GetTimeAsMsSinceEpoch(time.Now()) + 120000 _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 = dataprovider.GCSFilesystemProvider u.FsConfig.GCSConfig.Bucket = "testbucket" u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(`{ "type": "service_account" }`) providerConf := config.GetProviderConf() providerConf.PreferDatabaseCredentials = true credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username)) if !filepath.IsAbs(credentialsFile) { credentialsFile = filepath.Join(configDir, credentialsFile) } assert.NoError(t, dataprovider.Close()) err := dataprovider.Initialize(providerConf, configDir, true) assert.NoError(t, err) if _, err = os.Stat(credentialsFile); err == nil { // remove the credentials file assert.NoError(t, os.Remove(credentialsFile)) } user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) assert.Equal(t, kms.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()) assert.NoFileExists(t, credentialsFile) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() } _, err = httpdtest.RemoveUser(user, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) assert.NoError(t, err) assert.NoError(t, dataprovider.Close()) assert.NoError(t, config.LoadConfig(configDir, "")) providerConf = config.GetProviderConf() assert.NoError(t, dataprovider.Initialize(providerConf, configDir, true)) } func TestLoginInvalidFs(t *testing.T) { usePubKey := true u := getTestUser(usePubKey) u.FsConfig.Provider = dataprovider.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) providerConf := config.GetProviderConf() credentialsFile := filepath.Join(providerConf.CredentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username)) if !filepath.IsAbs(credentialsFile) { credentialsFile = filepath.Join(configDir, credentialsFile) } // now remove the credentials file so the filesystem creation will fail err = os.Remove(credentialsFile) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if !assert.Error(t, err, "login must fail, the user has an invalid filesystem config") { client.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) client, err := getSftpClient(user, true) if !assert.Error(t, err, "SSH protocol is disabled, authentication must fail") { client.Close() } user.Filters.DeniedProtocols = []string{common.ProtocolFTP, common.ProtocolWebDAV} user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, true) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, true) if !assert.Error(t, err, "public key login is disabled, authentication must fail") { client.Close() } user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.LoginMethodPassword} user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, true) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } user.Password = defaultPassword user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, false) if !assert.Error(t, err, "password login is disabled, authentication must fail") { client.Close() } user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.SSHLoginMethodPublicKey} user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, false) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err, "login from an not allowed IP must fail") { client.Close() } _, err = httpdtest.RemoveUser(user, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) 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 = "" user.PublicKeys = []string{} // password and public key should remain unchanged _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 TestLoginAfterUserUpdateEmptyPubKey(t *testing.T) { usePubKey := true user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated) assert.NoError(t, err) user.Password = "" user.PublicKeys = []string{} // password and public key should remain unchanged _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 = ioutil.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, 1), os.ModePerm) assert.NoError(t, err) client, err := getKeyboardInteractiveSftpClient(user, []string{"1", "2"}) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } user.Status = 0 user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"}) if !assert.Error(t, err, "keyboard interactive auth must fail the user is disabled") { client.Close() } user.Status = 1 user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) err = ioutil.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, -1), os.ModePerm) assert.NoError(t, err) client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"}) if !assert.Error(t, err, "keyboard interactive auth must fail the script returned -1") { client.Close() } err = ioutil.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, true, 1), os.ModePerm) assert.NoError(t, err) client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"}) if !assert.Error(t, err, "keyboard interactive auth must fail the script returned bad json") { client.Close() } err = ioutil.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 5, true, 1), os.ModePerm) assert.NoError(t, err) client, err = getKeyboardInteractiveSftpClient(user, []string{"1", "2"}) if !assert.Error(t, err, "keyboard interactive auth must fail the script returned bad json") { client.Close() } _, err = httpdtest.RemoveUser(user, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) 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 = ioutil.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) client, err := getSftpClient(u, usePubKey) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(user, true), os.ModePerm) assert.NoError(t, err) client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err, "pre-login script returned a non json response, login must fail") { client.Close() } user.Status = 0 err = ioutil.WriteFile(preLoginPath, getPreLoginScriptContent(user, false), os.ModePerm) assert.NoError(t, err) client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err, "pre-login script returned a disabled user, login must fail") { client.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 = ioutil.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.GetUserByUsername(defaultUsername, http.StatusNotFound) assert.NoError(t, err) client, err := getSftpClient(u, usePubKey) if assert.NoError(t, err) { 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 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 = ioutil.WriteFile(postConnectPath, getPostConnectScriptContent(0), os.ModePerm) assert.NoError(t, err) client, err := getSftpClient(u, usePubKey) if assert.NoError(t, err) { defer client.Close() err = checkBasicSFTP(client) assert.NoError(t, err) } err = ioutil.WriteFile(postConnectPath, getPostConnectScriptContent(1), os.ModePerm) assert.NoError(t, err) client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err) { client.Close() } common.Config.PostConnectHook = "http://127.0.0.1:8080/healthz" client, err = getSftpClient(u, usePubKey) if assert.NoError(t, err) { defer client.Close() err = checkBasicSFTP(client) assert.NoError(t, err) } common.Config.PostConnectHook = "http://127.0.0.1:8080/notfound" client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err) { client.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 = ioutil.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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { err = checkBasicSFTP(client) assert.NoError(t, err) client.Close() } err = ioutil.WriteFile(checkPwdPath, getCheckPwdScriptsContents(0, defaultPassword), os.ModePerm) assert.NoError(t, err) client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err) { client.Close() } err = ioutil.WriteFile(checkPwdPath, getCheckPwdScriptsContents(1, ""), os.ModePerm) assert.NoError(t, err) user.Password = defaultPassword + "1" client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { err = checkBasicSFTP(client) assert.NoError(t, err) client.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" client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err) { client.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 = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, 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) client, err := getSftpClient(u, usePubKey) if assert.NoError(t, err) { 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" client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err, "external auth login with invalid user must fail") { client.Close() } usePubKey = false u = getTestUser(usePubKey) u.PublicKeys = []string{} err = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm) assert.NoError(t, err) client, err = getSftpClient(u, usePubKey) if assert.NoError(t, err) { 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) _, 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 = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, 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) client, err := getSftpClient(u, usePubKey) if assert.NoError(t, err) { 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 client, err = getSftpClient(u, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath, }, 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 = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm) assert.NoError(t, err) providerConf.ExternalAuthHook = extAuthPath providerConf.ExternalAuthScope = authScope err = dataprovider.Initialize(providerConf, configDir, true) assert.NoError(t, err) client, err := getSftpClient(u, usePubKey) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } u.Username = defaultUsername + "1" client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err, "external auth login with invalid user must fail") { client.Close() } usePubKey = !usePubKey u = getTestUser(usePubKey) client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err, "external auth login with valid user but invalid auth scope must fail") { client.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 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 = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm) assert.NoError(t, err) providerConf.ExternalAuthHook = extAuthPath providerConf.ExternalAuthScope = 4 err = dataprovider.Initialize(providerConf, configDir, true) assert.NoError(t, err) err = ioutil.WriteFile(keyIntAuthPath, getKeyboardInteractiveScriptContent([]string{"1", "2"}, 0, false, 1), os.ModePerm) assert.NoError(t, err) client, err := getKeyboardInteractiveSftpClient(u, []string{"1", "2"}) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } u.Username = defaultUsername + "1" client, err = getKeyboardInteractiveSftpClient(u, []string{"1", "2"}) if !assert.Error(t, err, "external auth login with invalid user must fail") { client.Close() } usePubKey = true u = getTestUser(usePubKey) client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err, "external auth login with valid user but invalid auth scope must fail") { client.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 = ioutil.WriteFile(extAuthPath, getExtAuthScriptContent(u, true, ""), os.ModePerm) assert.NoError(t, err) providerConf.ExternalAuthHook = extAuthPath providerConf.ExternalAuthScope = 0 err = dataprovider.Initialize(providerConf, configDir, true) assert.NoError(t, err) client, err := getSftpClient(u, usePubKey) if !assert.Error(t, err, "login must fail, external auth returns a non json response") { client.Close() } usePubKey = false u = getTestUser(usePubKey) client, err = getSftpClient(u, usePubKey) if !assert.Error(t, err, "login must fail, external auth returns a non json response") { client.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 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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") 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) } func TestMaxConnections(t *testing.T) { oldValue := common.Config.MaxTotalConnections common.Config.MaxTotalConnections = 1 usePubKey := true u := getTestUser(usePubKey) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) c, err := getSftpClient(user, usePubKey) if !assert.Error(t, err, "max sessions exceeded, new login should not succeed") { c.Close() } } _, err = httpdtest.RemoveUser(user, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(user.GetHomeDir()) assert.NoError(t, err) common.Config.MaxTotalConnections = 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) c, err := getSftpClient(user, usePubKey) if !assert.Error(t, err, "max sessions exceeded, new login should not succeed") { c.Close() } } _, 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { //nolint:dupl 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) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) user.UsedQuotaFiles = 0 user.UsedQuotaSize = 0 _, err = httpdtest.UpdateQuotaUsage(user, "reset", http.StatusOK) assert.NoError(t, err) user.QuotaSize = 0 _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) } } _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK) assert.NoError(t, err) _, err = httpdtest.RemoveUser(localUser, http.StatusOK) assert.NoError(t, err) err = os.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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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") 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) user.UsedQuotaFiles = 0 user.UsedQuotaSize = 0 _, err = httpdtest.UpdateQuotaUsage(user, "reset", http.StatusOK) assert.NoError(t, err) } } } _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK) assert.NoError(t, err) _, err = httpdtest.RemoveUser(localUser, http.StatusOK) assert.NoError(t, err) err = os.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 client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() err = sftpUploadFile(testFilePath, testFileName+".quota", testFileSize, client) assert.NoError(t, err) err = sftpUploadFile(testFilePath, testFileName+".quota.1", testFileSize, client) assert.Error(t, err, "user is over quota files, upload must fail") // 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) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() err = sftpUploadFile(testFilePath, testFileName+".quota.1", testFileSize, client) assert.Error(t, err, "user is over quota size, upload must fail") 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) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() err = sftpUploadFile(testFilePath1, testFileName1, testFileSize1, client) assert.Error(t, err) _, 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) user.UsedQuotaFiles = 0 user.UsedQuotaSize = 0 _, err = httpdtest.UpdateQuotaUsage(user, "reset", http.StatusOK) 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 = 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 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) } //nolint:dupl 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 = []dataprovider.PatternsFilter{ { Path: "/", AllowedPatterns: []string{"*.zIp"}, DeniedPatterns: []string{}, }, } _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) } //nolint:dupl func TestExtensionsFilters(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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) err = sftpUploadFile(testFilePath, testFileName+".jpg", testFileSize, client) assert.NoError(t, err) } user.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ { Path: "/", AllowedExtensions: []string{".zIp", ".jPg"}, DeniedExtensions: []string{}, }, } user.Filters.FilePatterns = []dataprovider.PatternsFilter{ { Path: "/", AllowedPatterns: []string{"*.jPg", "*.zIp"}, DeniedPatterns: []string{}, }, } _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 = sftpDownloadFile(testFileName+".jpg", 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, MappedPath: mappedPath, }, VirtualPath: vdirPath, }) u.Permissions[testDir] = []string{dataprovider.PermCreateDirs} u.Permissions[testDir1] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermDelete} u.Permissions[path.Join(testDir1, "subdir")] = []string{dataprovider.PermCreateSymlinks, dataprovider.PermUpload, dataprovider.PermDelete} err := os.MkdirAll(mappedPath, os.ModePerm) assert.NoError(t, err) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() 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 upload permission on testDir, we can only create dirs err = client.Rename("vdir2", testDir) assert.Error(t, err) // on testDir1 only symlink aren't allowed 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) // we cannot create dirs inside /userDir1/subdir err = client.Rename("vdir2", testDir1) assert.Error(t, err) err = client.Rename("vdir2", "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, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, QuotaFiles: -1, QuotaSize: -1, }) u1.VirtualFolders = append(u1.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, 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, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, QuotaFiles: -1, QuotaSize: -1, }) u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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.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) } err = os.Remove(testFilePath) 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, MappedPath: mappedPath, }, VirtualPath: vdirPath, QuotaFiles: 10, }) 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() data := []byte("test data") 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(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) 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) user.UsedQuotaFiles = 0 user.UsedQuotaSize = 0 _, err = httpdtest.UpdateQuotaUsage(user, "reset", http.StatusOK) assert.NoError(t, err) user.QuotaSize = 0 _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) } } } _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK) assert.NoError(t, err) _, err = httpdtest.RemoveUser(localUser, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(localUser.GetHomeDir()) assert.NoError(t, err) _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: 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, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, QuotaFiles: 2, QuotaSize: 0, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ MappedPath: mappedPath2, Name: folderName2, }, VirtualPath: vdirPath2, QuotaFiles: 0, QuotaSize: testFileSize + testFileSize1 + 1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName3, MappedPath: mappedPath3, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath1, }, 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, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath1, }, 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, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath1, }, 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, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath1, }, 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, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath1, }, 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, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath1, }, 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, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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(testFileName, path.Join(vdirPath1, testFileName+".link1")) assert.Error(t, err) err = client.Symlink(testFileName, path.Join(vdirPath1, testDir, testFileName+".link1")) assert.Error(t, err) err = client.Symlink(testFileName, path.Join(vdirPath2, testFileName+".link1")) assert.Error(t, err) err = client.Symlink(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 TestOverlappedMappedFolders(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) subDir := "subdir" mappedPath1 := filepath.Join(os.TempDir(), "vdir1") folderName1 := filepath.Base(mappedPath1) vdirPath1 := "/vdir1" mappedPath2 := filepath.Join(os.TempDir(), "vdir1", subDir) folderName2 := filepath.Base(mappedPath2) vdirPath2 := "/vdir2" u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName1, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, VirtualPath: vdirPath2, }) 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() err = checkBasicSFTP(client) assert.NoError(t, err) 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) 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) fi, err := client.Stat(path.Join(vdirPath1, subDir, testFileName)) if assert.NoError(t, err) { assert.Equal(t, testFileSize, fi.Size()) } err = client.Rename(path.Join(vdirPath1, subDir, testFileName), path.Join(vdirPath2, testFileName+"1")) assert.NoError(t, err) err = client.Rename(path.Join(vdirPath2, testFileName+"1"), path.Join(vdirPath1, subDir, testFileName)) assert.NoError(t, err) err = client.Rename(path.Join(vdirPath1, subDir), path.Join(vdirPath2, subDir)) assert.Error(t, err) err = client.Mkdir(subDir) assert.NoError(t, err) err = client.Rename(subDir, path.Join(vdirPath1, subDir)) assert.Error(t, err) err = client.RemoveDirectory(path.Join(vdirPath1, subDir)) assert.Error(t, err) err = client.Symlink(path.Join(vdirPath1, subDir), path.Join(vdirPath1, "adir")) assert.Error(t, err) err = client.Mkdir(path.Join(vdirPath1, subDir+"1")) assert.NoError(t, err) err = client.Symlink(path.Join(vdirPath1, subDir+"1"), path.Join(vdirPath1, subDir)) assert.Error(t, err) err = os.Remove(testFilePath) assert.NoError(t, err) _, err = runSSHCommand(fmt.Sprintf("sftpgo-remove %v", path.Join(vdirPath1, subDir)), user, usePubKey) assert.Error(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) if providerConf.Driver != dataprovider.MemoryDataProviderName { client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err) { client.Close() } _, 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.AddUser(u, http.StatusCreated) assert.Error(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 TestResolveOverlappedMappedPaths(t *testing.T) { u := getTestUser(false) mappedPath1 := filepath.Join(os.TempDir(), "mapped1", "subdir") folderName1 := filepath.Base(mappedPath1) vdirPath1 := "/vdir1/subdir" mappedPath2 := filepath.Join(os.TempDir(), "mapped2") folderName2 := filepath.Base(mappedPath2) vdirPath2 := "/vdir2/subdir" mappedPath3 := filepath.Join(os.TempDir(), "mapped1") folderName3 := filepath.Base(mappedPath3) vdirPath3 := "/vdir3" mappedPath4 := filepath.Join(os.TempDir(), "mapped1", "subdir", "vdir4") folderName4 := filepath.Base(mappedPath4) vdirPath4 := "/vdir4" u.VirtualFolders = []vfs.VirtualFolder{ { BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName1, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, }, { BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, VirtualPath: vdirPath2, }, { BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName3, MappedPath: mappedPath3, }, VirtualPath: vdirPath3, }, { BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName4, MappedPath: mappedPath4, }, VirtualPath: vdirPath4, }, } err := os.MkdirAll(u.GetHomeDir(), os.ModePerm) assert.NoError(t, err) 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) err = os.MkdirAll(mappedPath4, os.ModePerm) assert.NoError(t, err) fs := vfs.NewOsFs("", u.GetHomeDir(), u.VirtualFolders) p, err := fs.ResolvePath("/vdir1") assert.NoError(t, err) assert.Equal(t, filepath.Join(u.GetHomeDir(), "vdir1"), p) p, err = fs.ResolvePath("/vdir1/subdir") assert.NoError(t, err) assert.Equal(t, mappedPath1, p) p, err = fs.ResolvePath("/vdir3/subdir/vdir4/file.txt") assert.NoError(t, err) assert.Equal(t, filepath.Join(mappedPath4, "file.txt"), p) p, err = fs.ResolvePath("/vdir4/file.txt") assert.NoError(t, err) assert.Equal(t, filepath.Join(mappedPath4, "file.txt"), p) assert.Equal(t, filepath.Join(mappedPath3, "subdir", "vdir4", "file.txt"), p) assert.Equal(t, filepath.Join(mappedPath1, "vdir4", "file.txt"), p) p, err = fs.ResolvePath("/vdir3/subdir/vdir4/../file.txt") assert.NoError(t, err) assert.Equal(t, filepath.Join(mappedPath3, "subdir", "file.txt"), p) assert.Equal(t, filepath.Join(mappedPath1, "file.txt"), p) err = os.RemoveAll(u.GetHomeDir()) assert.NoError(t, err) err = os.RemoveAll(mappedPath4) assert.NoError(t, err) err = os.RemoveAll(mappedPath1) assert.NoError(t, err) err = os.RemoveAll(mappedPath3) 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, MappedPath: mappedPath1, }, 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, MappedPath: mappedPath2, }, 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) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err = getSftpClient(user1, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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") err = client.Mkdir("test") 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 = os.Chmod(user.GetHomeDir(), os.ModePerm) assert.NoError(t, err) err = os.Chmod(filepath.Join(user.GetHomeDir(), "test"), 0000) assert.NoError(t, err) err = client.Rename(testFileName, path.Join("test", testFileName)) assert.True(t, os.IsPermission(err)) err = os.Chmod(filepath.Join(user.GetHomeDir(), "test"), 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 pwdMapping := make(map[string]string) pwdMapping["$pbkdf2-sha1$150000$DveVjgYUD05R$X6ydQZdyMeOvpgND2nqGR/0GGic="] = "password" //nolint:goconst pwdMapping["$pbkdf2-sha256$150000$E86a9YMX3zC7$R5J62hsSq+pYw00hLLPKBbcGXmq7fj5+/M0IFoYtZbo="] = "password" pwdMapping["$pbkdf2-sha512$150000$dsu7T5R3IaVQ$1hFXPO1ntRBcoWkSLKw+s4sAP09Xtu4Ya7CyxFq64jM9zdUg8eRJVr3NcR2vQgb0W9HHvZaILHsL4Q/Vr6arCg=="] = "password" pwdMapping["$1$b5caebda$VODr/nyhGWgZaY8sJ4x05."] = "password" pwdMapping["$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK"] = "secret" pwdMapping["$6$459ead56b72e44bc$uog86fUxscjt28BZxqFBE2pp2QD8P/1e98MNF75Z9xJfQvOckZnQ/1YJqiq1XeytPuDieHZvDAMoP7352ELkO1"] = "secret" pwdMapping["$apr1$OBWLeSme$WoJbB736e7kKxMBIAqilb1"] = "password" for pwd, clearPwd := range pwdMapping { u := getTestUser(usePubKey) u.Password = pwd user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) user.Password = clearPwd client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err, "unable to login with password %#v", pwd) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } user.Password = pwd client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err, "login with wrong password must fail") { 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 client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() assert.NoError(t, checkBasicSFTP(client)) } user.Password = pbkdf2Pwd client, err = getSftpClient(user, usePubKey) if !assert.Error(t, err, "login with wrong password must fail") { client.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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() _, err = client.ReadDir(".") assert.Error(t, err, "read remote dir without permission should not succeed") _, err = client.Stat("test_file") assert.Error(t, err, "stat remote file without permission should not succeed") _, err = client.Lstat("test_file") assert.Error(t, err, "lstat remote file without permission should not succeed") _, err = client.ReadLink("test_link") assert.Error(t, err, "read remote link without permission on source dir 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(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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, os.IsPermission(err)) _, 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, os.IsPermission(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 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, os.IsPermission(err)) err = client.Symlink(testFileName, testFileNameSub+".link") assert.True(t, os.IsPermission(err)) err = client.Symlink(testFileName, testFileName+".link") assert.NoError(t, err) err = client.Rename(testFileName, testFileNameSub+".rename") assert.True(t, os.IsPermission(err)) 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, os.IsPermission(err)) 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, os.IsPermission(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) 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, os.IsPermission(err)) err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) assert.True(t, os.IsPermission(err)) err = client.Chtimes(testFileName, time.Now(), time.Now()) assert.True(t, os.IsPermission(err)) err = client.Rename(testFileName, testFileName+".rename") assert.True(t, os.IsPermission(err)) err = client.Symlink(testFileName, testFileName+".link") assert.True(t, os.IsPermission(err)) err = client.Remove(testFileName) assert.True(t, os.IsPermission(err)) 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, os.IsPermission(err)) err = client.Chtimes("subdir/", time.Now(), time.Now()) assert.True(t, os.IsPermission(err)) 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: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, Auth: []ssh.AuthMethod{ssh.Password(defaultPassword)}, } 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 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, os.IsPermission(err)) err = client.RemoveDirectory("/subdir/dir") assert.True(t, os.IsPermission(err)) err = client.Mkdir("/subdir/otherdir/dir") assert.True(t, os.IsPermission(err)) 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, os.IsPermission(err)) err = client.Symlink("/otherdir", "/subdir/otherdir") assert.True(t, os.IsPermission(err)) 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { defer client.Close() err = client.Rename("/", "rootdir") assert.True(t, os.IsPermission(err)) err = client.Symlink("/", "rootdir") assert.True(t, os.IsPermission(err)) err = client.RemoveDirectory("/") assert.True(t, os.IsPermission(err)) } if user.Username == defaultUsername { err = os.RemoveAll(user.GetHomeDir()) assert.NoError(t, err) user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") assert.NoError(t, err) } } _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK) assert.NoError(t, err) _, err = httpdtest.RemoveUser(localUser, http.StatusOK) assert.NoError(t, err) err = os.RemoveAll(localUser.GetHomeDir()) assert.NoError(t, err) } func TestRelativePaths(t *testing.T) { user := getTestUser(true) var path, rel string filesystems := []vfs.Fs{vfs.NewOsFs("", user.GetHomeDir(), user.VirtualFolders)} keyPrefix := strings.TrimPrefix(user.GetHomeDir(), "/") + "/" s3config := vfs.S3FsConfig{ KeyPrefix: keyPrefix, } s3fs, _ := vfs.NewS3Fs("", user.GetHomeDir(), s3config) gcsConfig := vfs.GCSFsConfig{ KeyPrefix: keyPrefix, } gcsfs, _ := vfs.NewGCSFs("", user.GetHomeDir(), gcsConfig) sftpconfig := vfs.SFTPFsConfig{ Endpoint: sftpServerAddr, Username: defaultUsername, Password: kms.NewPlainSecret(defaultPassword), Prefix: keyPrefix, } sftpfs, _ := vfs.NewSFTPFs("", 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(), user.VirtualFolders)} keyPrefix := strings.TrimPrefix(user.GetHomeDir(), "/") + "/" s3config := vfs.S3FsConfig{ 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{ 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(), "vdir") 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) fs := vfs.NewOsFs("", user.GetHomeDir(), user.VirtualFolders) rel := fs.GetRelativePath(mappedPath) assert.Equal(t, vdirPath, rel) rel = fs.GetRelativePath(filepath.Join(mappedPath, "..")) assert.Equal(t, "/", rel) // path outside home and virtual dir rel = fs.GetRelativePath(filepath.Join(mappedPath, "../vdir1")) assert.Equal(t, "/", rel) rel = fs.GetRelativePath(filepath.Join(mappedPath, "../vdir/file.txt")) assert.Equal(t, "/vdir/file.txt", rel) rel = fs.GetRelativePath(filepath.Join(user.HomeDir, "vdir1/file.txt")) assert.Equal(t, "/vdir1/file.txt", rel) } func TestResolveVirtualPaths(t *testing.T) { user := getTestUser(true) mappedPath := filepath.Join(os.TempDir(), "vdir") vdirPath := "/vdir" user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ MappedPath: mappedPath, }, VirtualPath: vdirPath, }) err := os.MkdirAll(mappedPath, os.ModePerm) assert.NoError(t, err) osFs := vfs.NewOsFs("", user.GetHomeDir(), user.VirtualFolders).(*vfs.OsFs) b, f := osFs.GetFsPaths("/vdir/a.txt") assert.Equal(t, mappedPath, b) assert.Equal(t, filepath.Join(mappedPath, "a.txt"), f) b, f = osFs.GetFsPaths("/vdir/sub with space & spécial chars/a.txt") assert.Equal(t, mappedPath, b) assert.Equal(t, filepath.Join(mappedPath, "sub with space & spécial chars/a.txt"), f) b, f = osFs.GetFsPaths("/vdir/../a.txt") assert.Equal(t, user.GetHomeDir(), b) assert.Equal(t, filepath.Join(user.GetHomeDir(), "a.txt"), f) b, f = osFs.GetFsPaths("/vdir1/a.txt") assert.Equal(t, user.GetHomeDir(), b) assert.Equal(t, filepath.Join(user.GetHomeDir(), "/vdir1/a.txt"), f) } 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")) } //nolint:dupl func TestFilterFilePatterns(t *testing.T) { user := getTestUser(true) pattern := dataprovider.PatternsFilter{ Path: "/test", AllowedPatterns: []string{"*.jpg", "*.png"}, DeniedPatterns: []string{"*.pdf"}, } filters := dataprovider.UserFilters{ FilePatterns: []dataprovider.PatternsFilter{pattern}, } user.Filters = filters assert.True(t, user.IsFileAllowed("/test/test.jPg")) assert.False(t, user.IsFileAllowed("/test/test.pdf")) assert.True(t, user.IsFileAllowed("/test.pDf")) filters.FilePatterns = append(filters.FilePatterns, dataprovider.PatternsFilter{ Path: "/", AllowedPatterns: []string{"*.zip", "*.rar", "*.pdf"}, DeniedPatterns: []string{"*.gz"}, }) user.Filters = filters assert.False(t, user.IsFileAllowed("/test1/test.gz")) assert.True(t, user.IsFileAllowed("/test1/test.zip")) assert.False(t, user.IsFileAllowed("/test/sub/test.pdf")) assert.False(t, user.IsFileAllowed("/test1/test.png")) filters.FilePatterns = append(filters.FilePatterns, dataprovider.PatternsFilter{ Path: "/test/sub", DeniedPatterns: []string{"*.tar"}, }) user.Filters = filters assert.False(t, user.IsFileAllowed("/test/sub/sub/test.tar")) assert.True(t, user.IsFileAllowed("/test/sub/test.gz")) assert.False(t, user.IsFileAllowed("/test/test.zip")) } //nolint:dupl func TestFilterFileExtensions(t *testing.T) { user := getTestUser(true) extension := dataprovider.ExtensionsFilter{ Path: "/test", AllowedExtensions: []string{".jpg", ".png"}, DeniedExtensions: []string{".pdf"}, } filters := dataprovider.UserFilters{ FileExtensions: []dataprovider.ExtensionsFilter{extension}, } user.Filters = filters assert.True(t, user.IsFileAllowed("/test/test.jPg")) assert.False(t, user.IsFileAllowed("/test/test.pdf")) assert.True(t, user.IsFileAllowed("/test.pDf")) filters.FileExtensions = append(filters.FileExtensions, dataprovider.ExtensionsFilter{ Path: "/", AllowedExtensions: []string{".zip", ".rar", ".pdf"}, DeniedExtensions: []string{".gz"}, }) user.Filters = filters assert.False(t, user.IsFileAllowed("/test1/test.gz")) assert.True(t, user.IsFileAllowed("/test1/test.zip")) assert.False(t, user.IsFileAllowed("/test/sub/test.pdf")) assert.False(t, user.IsFileAllowed("/test1/test.png")) filters.FileExtensions = append(filters.FileExtensions, dataprovider.ExtensionsFilter{ Path: "/test/sub", DeniedExtensions: []string{".tar"}, }) user.Filters = filters assert.False(t, user.IsFileAllowed("/test/sub/sub/test.tar")) assert.True(t, user.IsFileAllowed("/test/sub/test.gz")) assert.False(t, user.IsFileAllowed("/test/test.zip")) } func TestUserAllowedLoginMethods(t *testing.T) { user := getTestUser(true) user.Filters.DeniedLoginMethods = dataprovider.ValidSSHLoginMethods 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, 2, len(allowedMethods)) assert.True(t, utils.IsStringInSlice(dataprovider.SSHLoginMethodKeyAndKeyboardInt, allowedMethods)) assert.True(t, utils.IsStringInSlice(dataprovider.SSHLoginMethodKeyAndPassword, allowedMethods)) } func TestUserPartialAuth(t *testing.T) { user := getTestUser(true) user.Filters.DeniedLoginMethods = []string{ dataprovider.LoginMethodPassword, dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyboardInteractive, } assert.False(t, user.IsPartialAuth(dataprovider.LoginMethodPassword)) assert.False(t, user.IsPartialAuth(dataprovider.SSHLoginMethodKeyboardInteractive)) assert.True(t, user.IsPartialAuth(dataprovider.SSHLoginMethodPublicKey)) user.Filters.DeniedLoginMethods = []string{ dataprovider.LoginMethodPassword, dataprovider.SSHLoginMethodKeyboardInteractive, } assert.False(t, user.IsPartialAuth(dataprovider.SSHLoginMethodPublicKey)) user.Filters.DeniedLoginMethods = []string{ dataprovider.LoginMethodPassword, dataprovider.SSHLoginMethodPublicKey, } assert.False(t, user.IsPartialAuth(dataprovider.SSHLoginMethodPublicKey)) } func TestUserGetNextAuthMethods(t *testing.T) { user := getTestUser(true) user.Filters.DeniedLoginMethods = []string{ dataprovider.LoginMethodPassword, dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyboardInteractive, } methods := user.GetNextAuthMethods(nil, true) assert.Equal(t, 0, len(methods)) methods = user.GetNextAuthMethods([]string{dataprovider.LoginMethodPassword}, true) assert.Equal(t, 0, len(methods)) methods = user.GetNextAuthMethods([]string{dataprovider.SSHLoginMethodKeyboardInteractive}, true) assert.Equal(t, 0, len(methods)) methods = user.GetNextAuthMethods([]string{ dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyboardInteractive, }, true) assert.Equal(t, 0, len(methods)) methods = user.GetNextAuthMethods([]string{dataprovider.SSHLoginMethodPublicKey}, true) assert.Equal(t, 2, len(methods)) assert.True(t, utils.IsStringInSlice(dataprovider.LoginMethodPassword, methods)) assert.True(t, utils.IsStringInSlice(dataprovider.SSHLoginMethodKeyboardInteractive, methods)) methods = user.GetNextAuthMethods([]string{dataprovider.SSHLoginMethodPublicKey}, false) assert.Equal(t, 1, len(methods)) assert.True(t, utils.IsStringInSlice(dataprovider.SSHLoginMethodKeyboardInteractive, methods)) user.Filters.DeniedLoginMethods = []string{ dataprovider.LoginMethodPassword, dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.SSHLoginMethodKeyAndKeyboardInt, } methods = user.GetNextAuthMethods([]string{dataprovider.SSHLoginMethodPublicKey}, true) assert.Equal(t, 1, len(methods)) assert.True(t, utils.IsStringInSlice(dataprovider.LoginMethodPassword, methods)) user.Filters.DeniedLoginMethods = []string{ dataprovider.LoginMethodPassword, dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.SSHLoginMethodKeyAndPassword, } methods = user.GetNextAuthMethods([]string{dataprovider.SSHLoginMethodPublicKey}, true) assert.Equal(t, 1, len(methods)) assert.True(t, utils.IsStringInSlice(dataprovider.SSHLoginMethodKeyboardInteractive, methods)) } 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, nil)) assert.False(t, user.IsLoginMethodAllowed(dataprovider.SSHLoginMethodPublicKey, nil)) assert.False(t, user.IsLoginMethodAllowed(dataprovider.SSHLoginMethodKeyboardInteractive, nil)) assert.True(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, []string{dataprovider.SSHLoginMethodPublicKey})) assert.True(t, user.IsLoginMethodAllowed(dataprovider.SSHLoginMethodKeyboardInteractive, []string{dataprovider.SSHLoginMethodPublicKey})) assert.True(t, user.IsLoginMethodAllowed(dataprovider.SSHLoginMethodKeyAndPassword, []string{dataprovider.SSHLoginMethodPublicKey})) user.Filters.DeniedLoginMethods = []string{ dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodKeyboardInteractive, } assert.True(t, user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, nil)) } 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 denied assert.False(t, user.IsLoginFromAddrAllowed("192.168.1.5")) 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 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} { client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, QuotaFiles: -1, QuotaSize: -1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, VirtualPath: vdirPath2, QuotaFiles: 100, QuotaSize: 0, }) u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ { Path: "/", DeniedExtensions: []string{".denied"}, }, } 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) testDir := "adir" testDir1 := "adir1" client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 %v", 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 %v %v", testFileName, testFileName+".linkcopy"), user, usePubKey) assert.Error(t, err) out, err := runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", 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) assert.Error(t, err) out, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath2, testDir1, testFileName), testFileName+".copy"), user, usePubKey) if assert.NoError(t, err) { assert.Equal(t, "OK\n", string(out)) fi, err := client.Stat(testFileName + ".copy") 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")), 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) } _, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath2, ".."), "newdir"), user, usePubKey) assert.Error(t, err) _, 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) // c.connection.fs.GetDirSize(fsSourcePath) will fail scanning subdirs // checkRecursiveCopyPermissions will work since it will skip subdirs with no permissions _, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", vdirPath1, "newdir"), 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.RemoveAll(filepath.Join(user.GetHomeDir(), "vdir1")) assert.NoError(t, err) err = os.Chmod(user.GetHomeDir(), 0555) assert.NoError(t, err) _, err = runSSHCommand(fmt.Sprintf("sftpgo-copy %v %v", path.Join(vdirPath2), "/vdir1"), user, usePubKey) assert.Error(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} u.Permissions["/dir3"] = []string{dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermDownload, dataprovider.PermListItems} user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 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.Error(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) _, 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, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, QuotaFiles: -1, QuotaSize: -1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, VirtualPath: vdirPath2, QuotaFiles: 3, QuotaSize: testFileSize + testFileSize1 + 1, }) u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ { Path: "/", DeniedExtensions: []string{".denied"}, }, } 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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) 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) 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 user.VirtualFolders[1].QuotaSize = testFileSize user.VirtualFolders[1].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) { f := user.VirtualFolders[1] 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, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, QuotaFiles: -1, QuotaSize: -1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, VirtualPath: vdirPath2, QuotaFiles: 100, QuotaSize: 0, }) 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) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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.Error(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) client, err = getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 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 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, MappedPath: mappedPath, }, VirtualPath: "/" + repoName, QuotaFiles: 0, QuotaSize: 0, }) err := os.MkdirAll(mappedPath, os.ModePerm) assert.NoError(t, err) user, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { // we upload a file so the user is over quota 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 len(scpPath) == 0 { 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") err = scpUpload(testFilePath, remoteUpPath, false, false) assert.NoError(t, err) 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()) } 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(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 len(scpPath) == 0 { 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 client, err := getSftpClient(user, usePubKey) if assert.NoError(t, err) { 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 = 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 len(scpPath) == 0 { 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 = 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 TestSCPExtensionsFilter(t *testing.T) { if len(scpPath) == 0 { 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.FileExtensions = []dataprovider.ExtensionsFilter{ { Path: "/", AllowedExtensions: []string{".zip"}, DeniedExtensions: []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 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 len(scpPath) == 0 { 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, MappedPath: mappedPath, }, VirtualPath: vdirPath, }) 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 TestSCPVirtualFoldersQuota(t *testing.T) { if len(scpPath) == 0 { 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, MappedPath: mappedPath1, }, VirtualPath: vdirPath1, QuotaFiles: -1, QuotaSize: -1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ Name: folderName2, MappedPath: mappedPath2, }, VirtualPath: vdirPath2, QuotaFiles: 0, QuotaSize: 0, }) 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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 len(scpPath) == 0 { 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\n", address, err) time.Sleep(100 * time.Millisecond) continue } logger.InfoToConsole("tcp server %v now listening\n", address) conn.Close() break } } func getTestUser(usePubKey bool) dataprovider.User { user := dataprovider.User{ 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 = dataprovider.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: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, } 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) (*sftp.Client, error) { var sftpClient *sftp.Client config := &ssh.ClientConfig{ User: user.Username, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, } if usePubKey { signer, err := ssh.ParsePrivateKey([]byte(testPrivateKey)) if err != nil { return nil, err } config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} } else { if len(user.Password) > 0 { 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 sftpClient, err } sftpClient, err = sftp.NewClient(conn) return sftpClient, err } func getSftpClient(user dataprovider.User, usePubKey bool) (*sftp.Client, error) { return getSftpClientWithAddr(user, usePubKey, sftpServerAddr) } func getKeyboardInteractiveSftpClient(user dataprovider.User, answers []string) (*sftp.Client, error) { var sftpClient *sftp.Client config := &ssh.ClientConfig{ User: user.Username, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, Auth: []ssh.AuthMethod{ ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) { return answers, nil }), }, } conn, err := ssh.Dial("tcp", sftpServerAddr, config) if err != nil { return sftpClient, err } sftpClient, err = sftp.NewClient(conn) return sftpClient, err } func getCustomAuthSftpClient(user dataprovider.User, authMethods []ssh.AuthMethod, addr string) (*sftp.Client, error) { var sftpClient *sftp.Client config := &ssh.ClientConfig{ User: user.Username, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, Auth: authMethods, } var err error var conn *ssh.Client if len(addr) > 0 { conn, err = ssh.Dial("tcp", addr, config) } else { conn, err = ssh.Dial("tcp", sftpServerAddr, config) } if err != nil { return sftpClient, err } sftpClient, err = sftp.NewClient(conn) return sftpClient, err } func createTestFile(path string, size int64) error { baseDir := filepath.Dir(path) if _, err := os.Stat(baseDir); os.IsNotExist(err) { 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 ioutil.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 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 close method on server // we cannot defer closing or Lstat will fail for uploads in 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, 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 close method on server // we cannot defer closing or Lstat 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") } 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") } } 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, 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 = "" } } 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 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 bool, username string) []byte { extAuthContent := []byte("#!/bin/sh\n\n") extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...) if len(username) > 0 { 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 getPostConnectScriptContent(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 := ioutil.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 %#v: %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") err := ioutil.WriteFile(pubKeyPath, []byte(testPubKey+"\n"), 0600) if err != nil { logger.WarnToConsole("unable to save public key to file: %v", err) } err = ioutil.WriteFile(privateKeyPath, []byte(testPrivateKey+"\n"), 0600) if err != nil { logger.WarnToConsole("unable to save private key to file: %v", err) } err = ioutil.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 = ioutil.WriteFile(trustedCAUserKey, []byte(testCAUserKey), 0600) if err != nil { logger.WarnToConsole("unable to save trusted CA user key: %v", err) } }