package httpd_test import ( "bytes" "crypto/rand" "encoding/base64" "encoding/json" "fmt" "io" "io/ioutil" "mime/multipart" "net" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "runtime" "strconv" "strings" "testing" "time" "github.com/go-chi/render" _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" "github.com/rs/zerolog" "github.com/drakkan/sftpgo/config" "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/httpd" "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/sftpd" "github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/vfs" ) const ( defaultUsername = "test_user" defaultPassword = "test_password" testPubKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1" logSender = "APITesting" userPath = "/api/v1/user" activeConnectionsPath = "/api/v1/connection" quotaScanPath = "/api/v1/quota_scan" versionPath = "/api/v1/version" providerStatusPath = "/api/v1/providerstatus" dumpDataPath = "/api/v1/dumpdata" loadDataPath = "/api/v1/loaddata" metricsPath = "/metrics" pprofPath = "/debug/pprof/" webBasePath = "/web" webUsersPath = "/web/users" webUserPath = "/web/user" webConnectionsPath = "/web/connections" configDir = ".." httpsCert = `-----BEGIN CERTIFICATE----- MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw OTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQA IgNiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVqWvrJ51t5OxV0v25NsOgR82CA NXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIVCzgWkxiz7XE4lgUwX44FCXZM 3+JeUbKjUzBRMB0GA1UdDgQWBBRhLw+/o3+Z02MI/d4tmaMui9W16jAfBgNVHSME GDAWgBRhLw+/o3+Z02MI/d4tmaMui9W16jAPBgNVHRMBAf8EBTADAQH/MAoGCCqG SM49BAMCA2kAMGYCMQDqLt2lm8mE+tGgtjDmtFgdOcI72HSbRQ74D5rYTzgST1rY /8wTi5xl8TiFUyLMUsICMQC5ViVxdXbhuG7gX6yEqSkMKZICHpO8hqFwOD/uaFVI dV4vKmHUzwK/eIx+8Ay3neE= -----END CERTIFICATE-----` httpsKey = `-----BEGIN EC PARAMETERS----- BgUrgQQAIg== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- MIGkAgEBBDCfMNsN6miEE3rVyUPwElfiJSWaR5huPCzUenZOfJT04GAcQdWvEju3 UM2lmBLIXpGgBwYFK4EEACKhZANiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVq WvrJ51t5OxV0v25NsOgR82CANXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIV CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI= -----END EC PRIVATE KEY-----` ) var ( defaultPerms = []string{dataprovider.PermAny} homeBasePath string backupsPath string credentialsPath string testServer *httptest.Server providerDriverName string ) func TestMain(m *testing.M) { homeBasePath = os.TempDir() logfilePath := filepath.Join(configDir, "sftpgo_api_test.log") logger.InitLogger(logfilePath, 5, 1, 28, false, zerolog.DebugLevel) config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() credentialsPath = filepath.Join(os.TempDir(), "test_credentials") providerConf.CredentialsPath = credentialsPath providerDriverName = providerConf.Driver os.RemoveAll(credentialsPath) err := dataprovider.Initialize(providerConf, configDir) if err != nil { logger.Warn(logSender, "", "error initializing data provider: %v", err) os.Exit(1) } httpConfig := config.GetHTTPConfig() httpConfig.Initialize(configDir) dataProvider := dataprovider.GetProvider() httpdConf := config.GetHTTPDConfig() httpdConf.BindPort = 8081 httpd.SetBaseURLAndCredentials("http://127.0.0.1:8081", "", "") backupsPath = filepath.Join(os.TempDir(), "test_backups") httpdConf.BackupsPath = backupsPath os.MkdirAll(backupsPath, 0777) sftpd.SetDataProvider(dataProvider) httpd.SetDataProvider(dataProvider) go func() { if err := httpdConf.Initialize(configDir, true); err != nil { logger.Error(logSender, "", "could not start HTTP server: %v", err) } }() waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort)) // now start an https server certPath := filepath.Join(os.TempDir(), "test.crt") keyPath := filepath.Join(os.TempDir(), "test.key") ioutil.WriteFile(certPath, []byte(httpsCert), 0666) ioutil.WriteFile(keyPath, []byte(httpsKey), 0666) httpdConf.BindPort = 8443 httpdConf.CertificateFile = certPath httpdConf.CertificateKeyFile = keyPath go func() { if err := httpdConf.Initialize(configDir, true); err != nil { logger.Error(logSender, "", "could not start HTTPS server: %v", err) } }() waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort)) httpd.ReloadTLSCertificate() testServer = httptest.NewServer(httpd.GetHTTPRouter()) defer testServer.Close() exitCode := m.Run() os.Remove(logfilePath) os.RemoveAll(backupsPath) os.RemoveAll(credentialsPath) os.Remove(certPath) os.Remove(keyPath) os.Exit(exitCode) } func TestInitialization(t *testing.T) { config.LoadConfig(configDir, "") httpdConf := config.GetHTTPDConfig() httpdConf.BackupsPath = "test_backups" httpdConf.AuthUserFile = "invalid file" err := httpdConf.Initialize(configDir, true) if err == nil { t.Error("Inizialize must fail") } httpdConf.BackupsPath = backupsPath httpdConf.AuthUserFile = "" httpdConf.CertificateFile = "invalid file" httpdConf.CertificateKeyFile = "invalid file" err = httpdConf.Initialize(configDir, true) if err == nil { t.Error("Inizialize must fail") } httpdConf.CertificateFile = "" httpdConf.CertificateKeyFile = "" httpdConf.TemplatesPath = "." err = httpdConf.Initialize(configDir, true) if err == nil { t.Error("Inizialize must fail") } err = httpd.ReloadTLSCertificate() if err != nil { t.Error("realoding TLS Certificate must return nil error if no certificate is configured") } } func TestBasicUserHandling(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.MaxSessions = 10 user.QuotaSize = 4096 user.QuotaFiles = 2 user.UploadBandwidth = 128 user.DownloadBandwidth = 64 user.ExpirationDate = utils.GetTimeAsMsSinceEpoch(time.Now()) user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK) if err != nil { t.Errorf("unable to get users: %v", err) } if len(users) != 1 { t.Errorf("number of users mismatch, expected: 1, actual: %v", len(users)) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUserStatus(t *testing.T) { u := getTestUser() u.Status = 3 _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with bad status: %v", err) } u.Status = 0 user, _, err := httpd.AddUser(u, http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.Status = 2 _, _, err = httpd.UpdateUser(user, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error updating user with bad status: %v", err) } user.Status = 1 user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestAddUserNoCredentials(t *testing.T) { u := getTestUser() u.Password = "" u.PublicKeys = []string{} _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with no credentials: %v", err) } } func TestAddUserNoUsername(t *testing.T) { u := getTestUser() u.Username = "" _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with no home dir: %v", err) } } func TestAddUserNoHomeDir(t *testing.T) { u := getTestUser() u.HomeDir = "" _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with no home dir: %v", err) } } func TestAddUserInvalidHomeDir(t *testing.T) { u := getTestUser() u.HomeDir = "relative_path" _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid home dir: %v", err) } } func TestAddUserNoPerms(t *testing.T) { u := getTestUser() u.Permissions = make(map[string][]string) _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with no perms: %v", err) } u.Permissions["/"] = []string{} _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with no perms: %v", err) } } func TestAddUserInvalidPerms(t *testing.T) { u := getTestUser() u.Permissions["/"] = []string{"invalidPerm"} _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid perms: %v", err) } // permissions for root dir are mandatory u.Permissions["/"] = []string{} u.Permissions["/somedir"] = []string{dataprovider.PermAny} _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with no root dir perms: %v", err) } u.Permissions["/"] = []string{dataprovider.PermAny} u.Permissions["/subdir/.."] = []string{dataprovider.PermAny} _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid dir perms: %v", err) } } func TestAddUserInvalidFilters(t *testing.T) { u := getTestUser() u.Filters.AllowedIP = []string{"192.168.1.0/24", "192.168.2.0"} _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid filters: %v", err) } u.Filters.AllowedIP = []string{} u.Filters.DeniedIP = []string{"192.168.3.0/16", "invalid"} _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid filters: %v", err) } u.Filters.DeniedIP = []string{} u.Filters.DeniedLoginMethods = []string{"invalid"} _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid filters: %v", err) } u.Filters.DeniedLoginMethods = dataprovider.ValidSSHLoginMethods _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid filters: %v", err) } u.Filters.DeniedLoginMethods = []string{} u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ { Path: "relative", AllowedExtensions: []string{}, DeniedExtensions: []string{}, }, } _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid extensions filters: %v", err) } u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ { Path: "/", AllowedExtensions: []string{}, DeniedExtensions: []string{}, }, } _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid extensions filters: %v", err) } u.Filters.FileExtensions = []dataprovider.ExtensionsFilter{ { Path: "/subdir", AllowedExtensions: []string{".zip"}, DeniedExtensions: []string{}, }, { Path: "/subdir", AllowedExtensions: []string{".rar"}, DeniedExtensions: []string{".jpg"}, }, } _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid extensions filters: %v", err) } } func TestAddUserInvalidFsConfig(t *testing.T) { u := getTestUser() u.FsConfig.Provider = 1 u.FsConfig.S3Config.Bucket = "" _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } os.RemoveAll(credentialsPath) os.MkdirAll(credentialsPath, 0700) u.FsConfig.S3Config.Bucket = "test" u.FsConfig.S3Config.Region = "eu-west-1" u.FsConfig.S3Config.AccessKey = "access-key" u.FsConfig.S3Config.AccessSecret = "access-secret" u.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b" u.FsConfig.S3Config.StorageClass = "Standard" u.FsConfig.S3Config.KeyPrefix = "/somedir/subdir/" _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } u.FsConfig.S3Config.KeyPrefix = "" u.FsConfig.S3Config.UploadPartSize = 3 _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } u.FsConfig.S3Config.UploadPartSize = 0 u.FsConfig.S3Config.UploadConcurrency = -1 _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } u = getTestUser() u.FsConfig.Provider = 2 u.FsConfig.GCSConfig.Bucket = "" _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } u.FsConfig.GCSConfig.Bucket = "test" u.FsConfig.GCSConfig.StorageClass = "Standard" u.FsConfig.GCSConfig.KeyPrefix = "/somedir/subdir/" u.FsConfig.GCSConfig.Credentials = base64.StdEncoding.EncodeToString([]byte("test")) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } u.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir/" u.FsConfig.GCSConfig.Credentials = "" u.FsConfig.GCSConfig.AutomaticCredentials = 0 _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } u.FsConfig.GCSConfig.Credentials = "no base64 encoded" _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid fs config: %v", err) } } func TestAddUserInvalidVirtualFolders(t *testing.T) { u := getTestUser() u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "vdir", MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/", MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir", MappedPath: filepath.Join(u.GetHomeDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir", MappedPath: u.GetHomeDir(), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir", MappedPath: filepath.Join(u.GetHomeDir(), ".."), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir", MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir", MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1", MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir2", MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1", MappedPath: filepath.Join(os.TempDir(), "mapped_dir", "subdir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir2", MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1", MappedPath: filepath.Join(os.TempDir(), "mapped_dir"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir2", MappedPath: filepath.Join(os.TempDir(), "mapped_dir", "subdir"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1/subdir", MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1/../vdir1", MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } u.VirtualFolders = nil u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1/", MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1/subdir", MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), }) _, _, err = httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid virtual folder: %v", err) } } func TestUserPublicKey(t *testing.T) { u := getTestUser() invalidPubKey := "invalid" validPubKey := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1" u.PublicKeys = []string{invalidPubKey} _, _, err := httpd.AddUser(u, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error adding user with invalid pub key: %v", err) } u.PublicKeys = []string{validPubKey} user, _, err := httpd.AddUser(u, http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.PublicKeys = []string{validPubKey, invalidPubKey} _, _, err = httpd.UpdateUser(user, http.StatusBadRequest) if err != nil { t.Errorf("update user with invalid public key must fail: %v", err) } user.PublicKeys = []string{validPubKey, validPubKey, validPubKey} _, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUpdateUser(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.HomeDir = filepath.Join(homeBasePath, "testmod") user.UID = 33 user.GID = 101 user.MaxSessions = 10 user.QuotaSize = 4096 user.QuotaFiles = 2 user.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermDelete, dataprovider.PermDownload} user.Permissions["/subdir"] = []string{dataprovider.PermListItems, dataprovider.PermUpload} user.Filters.AllowedIP = []string{"192.168.1.0/24", "192.168.2.0/24"} user.Filters.DeniedIP = []string{"192.168.3.0/24", "192.168.4.0/24"} user.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPassword} user.Filters.FileExtensions = append(user.Filters.FileExtensions, dataprovider.ExtensionsFilter{ Path: "/subdir", AllowedExtensions: []string{".zip", ".rar"}, DeniedExtensions: []string{".jpg", ".png"}, }) user.UploadBandwidth = 1024 user.DownloadBandwidth = 512 user.VirtualFolders = nil user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir1", MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"), }) user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{ VirtualPath: "/vdir12/subdir", MappedPath: filepath.Join(os.TempDir(), "mapped_dir2"), }) user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } user.Permissions["/subdir"] = []string{} user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } if len(user.Permissions["/subdir"]) > 0 { t.Errorf("unexpected subdir permissions, must be empty") } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUserS3Config(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.FsConfig.Provider = 1 user.FsConfig.S3Config.Bucket = "test" user.FsConfig.S3Config.Region = "us-east-1" user.FsConfig.S3Config.AccessKey = "Server-Access-Key" user.FsConfig.S3Config.AccessSecret = "Server-Access-Secret" user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000" user.FsConfig.S3Config.UploadPartSize = 8 user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } user.Password = defaultPassword user.ID = 0 secret, _ := utils.EncryptData("Server-Access-Secret") user.FsConfig.S3Config.AccessSecret = secret user, _, err = httpd.AddUser(user, http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.FsConfig.Provider = 1 user.FsConfig.S3Config.Bucket = "test1" user.FsConfig.S3Config.Region = "us-east-1" user.FsConfig.S3Config.AccessKey = "Server-Access-Key1" user.FsConfig.S3Config.Endpoint = "http://localhost:9000" user.FsConfig.S3Config.KeyPrefix = "somedir/subdir" user.FsConfig.S3Config.UploadConcurrency = 5 user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } user.FsConfig.Provider = 0 user.FsConfig.S3Config.Bucket = "" user.FsConfig.S3Config.Region = "" user.FsConfig.S3Config.AccessKey = "" user.FsConfig.S3Config.AccessSecret = "" user.FsConfig.S3Config.Endpoint = "" user.FsConfig.S3Config.KeyPrefix = "" user.FsConfig.S3Config.UploadPartSize = 0 user.FsConfig.S3Config.UploadConcurrency = 0 user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } // test user without access key and access secret (shared config state) user.FsConfig.Provider = 1 user.FsConfig.S3Config.Bucket = "test1" user.FsConfig.S3Config.Region = "us-east-1" user.FsConfig.S3Config.AccessKey = "" user.FsConfig.S3Config.AccessSecret = "" user.FsConfig.S3Config.Endpoint = "" user.FsConfig.S3Config.KeyPrefix = "somedir/subdir" user.FsConfig.S3Config.UploadPartSize = 6 user.FsConfig.S3Config.UploadConcurrency = 4 user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUserGCSConfig(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } os.RemoveAll(credentialsPath) os.MkdirAll(credentialsPath, 0700) user.FsConfig.Provider = 2 user.FsConfig.GCSConfig.Bucket = "test" user.FsConfig.GCSConfig.Credentials = base64.StdEncoding.EncodeToString([]byte("fake credentials")) user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } user.Password = defaultPassword user.ID = 0 // the user will be added since the credentials file is found user, _, err = httpd.AddUser(user, http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } os.RemoveAll(credentialsPath) os.MkdirAll(credentialsPath, 0700) user.FsConfig.GCSConfig.Credentials = "" user.FsConfig.GCSConfig.AutomaticCredentials = 1 user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } user.FsConfig.Provider = 1 user.FsConfig.S3Config.Bucket = "test1" user.FsConfig.S3Config.Region = "us-east-1" user.FsConfig.S3Config.AccessKey = "Server-Access-Key1" user.FsConfig.S3Config.AccessSecret = "secret" user.FsConfig.S3Config.Endpoint = "http://localhost:9000" user.FsConfig.S3Config.KeyPrefix = "somedir/subdir" user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } user.FsConfig.Provider = 2 user.FsConfig.GCSConfig.Bucket = "test1" user.FsConfig.GCSConfig.Credentials = base64.StdEncoding.EncodeToString([]byte("fake credentials")) user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUpdateUserNoCredentials(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.Password = "" user.PublicKeys = []string{} // password and public key will be omitted from json serialization if empty and so they will remain unchanged // and no validation error will be raised _, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unexpected error updating user with no credentials: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUpdateUserEmptyHomeDir(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.HomeDir = "" _, _, err = httpd.UpdateUser(user, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error updating user with empty home dir: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUpdateUserInvalidHomeDir(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } user.HomeDir = "relative_path" _, _, err = httpd.UpdateUser(user, http.StatusBadRequest) if err != nil { t.Errorf("unexpected error updating user with empty home dir: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } } func TestUpdateNonExistentUser(t *testing.T) { _, _, err := httpd.UpdateUser(getTestUser(), http.StatusNotFound) if err != nil { t.Errorf("unable to update user: %v", err) } } func TestGetNonExistentUser(t *testing.T) { _, _, err := httpd.GetUserByID(0, http.StatusNotFound) if err != nil { t.Errorf("unable to get user: %v", err) } } func TestDeleteNonExistentUser(t *testing.T) { _, err := httpd.RemoveUser(getTestUser(), http.StatusNotFound) if err != nil { t.Errorf("unable to remove user: %v", err) } } func TestAddDuplicateUser(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } _, _, err = httpd.AddUser(getTestUser(), http.StatusInternalServerError) if err != nil { t.Errorf("unable to add second user: %v", err) } _, _, err = httpd.AddUser(getTestUser(), http.StatusOK) if err == nil { t.Errorf("adding a duplicate user must fail") } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove user: %v", err) } } func TestGetUsers(t *testing.T) { user1, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } u := getTestUser() u.Username = defaultUsername + "1" user2, _, err := httpd.AddUser(u, http.StatusOK) if err != nil { t.Errorf("unable to add second user: %v", err) } users, _, err := httpd.GetUsers(0, 0, "", http.StatusOK) if err != nil { t.Errorf("unable to get users: %v", err) } if len(users) < 2 { t.Errorf("at least 2 users are expected") } users, _, err = httpd.GetUsers(1, 0, "", http.StatusOK) if err != nil { t.Errorf("unable to get users: %v", err) } if len(users) != 1 { t.Errorf("1 user is expected") } users, _, err = httpd.GetUsers(1, 1, "", http.StatusOK) if err != nil { t.Errorf("unable to get users: %v", err) } if len(users) != 1 { t.Errorf("1 user is expected") } _, _, err = httpd.GetUsers(1, 1, "", http.StatusInternalServerError) if err == nil { t.Errorf("get users must succeed, we requested a fail for a good request") } _, err = httpd.RemoveUser(user1, http.StatusOK) if err != nil { t.Errorf("unable to remove user: %v", err) } _, err = httpd.RemoveUser(user2, http.StatusOK) if err != nil { t.Errorf("unable to remove user: %v", err) } } func TestGetQuotaScans(t *testing.T) { _, _, err := httpd.GetQuotaScans(http.StatusOK) if err != nil { t.Errorf("unable to get quota scans: %v", err) } _, _, err = httpd.GetQuotaScans(http.StatusInternalServerError) if err == nil { t.Errorf("quota scan request must succeed, we requested to check a wrong status code") } } func TestStartQuotaScan(t *testing.T) { user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } _, err = httpd.StartQuotaScan(user, http.StatusCreated) if err != nil { t.Errorf("unable to start quota scan: %v", err) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove user: %v", err) } } func TestGetVersion(t *testing.T) { _, _, err := httpd.GetVersion(http.StatusOK) if err != nil { t.Errorf("unable to get version: %v", err) } _, _, err = httpd.GetVersion(http.StatusInternalServerError) if err == nil { t.Errorf("get version request must succeed, we requested to check a wrong status code") } } func TestGetProviderStatus(t *testing.T) { _, _, err := httpd.GetProviderStatus(http.StatusOK) if err != nil { t.Errorf("unable to get provider status: %v", err) } _, _, err = httpd.GetProviderStatus(http.StatusBadRequest) if err == nil { t.Errorf("get provider status request must succeed, we requested to check a wrong status code") } } func TestGetConnections(t *testing.T) { _, _, err := httpd.GetConnections(http.StatusOK) if err != nil { t.Errorf("unable to get sftp connections: %v", err) } _, _, err = httpd.GetConnections(http.StatusInternalServerError) if err == nil { t.Errorf("get sftp connections request must succeed, we requested to check a wrong status code") } } func TestCloseActiveConnection(t *testing.T) { _, err := httpd.CloseConnection("non_existent_id", http.StatusNotFound) if err != nil { t.Errorf("unexpected error closing non existent sftp connection: %v", err) } } func TestUserBaseDir(t *testing.T) { dataProvider := dataprovider.GetProvider() dataprovider.Close(dataProvider) config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() providerConf.UsersBaseDir = homeBasePath err := dataprovider.Initialize(providerConf, configDir) if err != nil { t.Errorf("error initializing data provider with users base dir: %v", err) } httpd.SetDataProvider(dataprovider.GetProvider()) u := getTestUser() u.HomeDir = "" user, _, err := httpd.AddUser(getTestUser(), http.StatusOK) if err != nil { t.Errorf("unable to add user: %v", err) } if user.HomeDir != filepath.Join(providerConf.UsersBaseDir, u.Username) { t.Errorf("invalid home dir: %v", user.HomeDir) } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove: %v", err) } dataProvider = dataprovider.GetProvider() dataprovider.Close(dataProvider) config.LoadConfig(configDir, "") providerConf = config.GetProviderConf() providerConf.CredentialsPath = credentialsPath os.RemoveAll(credentialsPath) err = dataprovider.Initialize(providerConf, configDir) if err != nil { t.Errorf("error initializing data provider") } httpd.SetDataProvider(dataprovider.GetProvider()) sftpd.SetDataProvider(dataprovider.GetProvider()) } func TestProviderErrors(t *testing.T) { dataProvider := dataprovider.GetProvider() dataprovider.Close(dataProvider) _, _, err := httpd.GetUserByID(0, http.StatusInternalServerError) if err != nil { t.Errorf("get user with provider closed must fail: %v", err) } _, _, err = httpd.GetUsers(1, 0, defaultUsername, http.StatusInternalServerError) if err != nil { t.Errorf("get users with provider closed must fail: %v", err) } _, _, err = httpd.UpdateUser(dataprovider.User{}, http.StatusInternalServerError) if err != nil { t.Errorf("update user with provider closed must fail: %v", err) } _, err = httpd.RemoveUser(dataprovider.User{}, http.StatusInternalServerError) if err != nil { t.Errorf("delete user with provider closed must fail: %v", err) } _, _, err = httpd.GetProviderStatus(http.StatusInternalServerError) if err != nil { t.Errorf("get provider status with provider closed must fail: %v", err) } _, _, err = httpd.Dumpdata("backup.json", "", http.StatusInternalServerError) if err != nil { t.Errorf("get provider status with provider closed must fail: %v", err) } user := getTestUser() user.ID = 1 backupData := dataprovider.BackupData{} backupData.Users = append(backupData.Users, user) backupContent, _ := json.Marshal(backupData) backupFilePath := filepath.Join(backupsPath, "backup.json") ioutil.WriteFile(backupFilePath, backupContent, 0666) _, _, err = httpd.Loaddata(backupFilePath, "", "", http.StatusInternalServerError) if err != nil { t.Errorf("get provider status with provider closed must fail: %v", err) } os.Remove(backupFilePath) config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() providerConf.CredentialsPath = credentialsPath os.RemoveAll(credentialsPath) err = dataprovider.Initialize(providerConf, configDir) if err != nil { t.Errorf("error initializing data provider: %v", err) } httpd.SetDataProvider(dataprovider.GetProvider()) sftpd.SetDataProvider(dataprovider.GetProvider()) } func TestDumpdata(t *testing.T) { dataProvider := dataprovider.GetProvider() dataprovider.Close(dataProvider) config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() err := dataprovider.Initialize(providerConf, configDir) if err != nil { t.Errorf("error initializing data provider:_%v", err) } httpd.SetDataProvider(dataprovider.GetProvider()) sftpd.SetDataProvider(dataprovider.GetProvider()) _, _, err = httpd.Dumpdata("", "", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } _, _, err = httpd.Dumpdata(filepath.Join(backupsPath, "backup.json"), "", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } _, _, err = httpd.Dumpdata("../backup.json", "", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } _, _, err = httpd.Dumpdata("backup.json", "0", http.StatusOK) if err != nil { t.Errorf("unexpected error: %v", err) } _, _, err = httpd.Dumpdata("backup.json", "1", http.StatusOK) if err != nil { t.Errorf("unexpected error: %v", err) } os.Remove(filepath.Join(backupsPath, "backup.json")) if runtime.GOOS != "windows" { os.Chmod(backupsPath, 0001) _, _, err = httpd.Dumpdata("bck.json", "", http.StatusInternalServerError) if err != nil { t.Errorf("unexpected error: %v", err) } // subdir cannot be created _, _, err = httpd.Dumpdata(filepath.Join("subdir", "bck.json"), "", http.StatusInternalServerError) if err != nil { t.Errorf("unexpected error: %v", err) } os.Chmod(backupsPath, 0755) } dataProvider = dataprovider.GetProvider() dataprovider.Close(dataProvider) config.LoadConfig(configDir, "") providerConf = config.GetProviderConf() providerConf.CredentialsPath = credentialsPath os.RemoveAll(credentialsPath) err = dataprovider.Initialize(providerConf, configDir) if err != nil { t.Errorf("error initializing data provider: %v", err) } httpd.SetDataProvider(dataprovider.GetProvider()) sftpd.SetDataProvider(dataprovider.GetProvider()) } func TestLoaddata(t *testing.T) { user := getTestUser() user.ID = 1 user.Username = "test_user_restore" backupData := dataprovider.BackupData{} backupData.Users = append(backupData.Users, user) backupContent, _ := json.Marshal(backupData) backupFilePath := filepath.Join(backupsPath, "backup.json") ioutil.WriteFile(backupFilePath, backupContent, 0666) _, _, err := httpd.Loaddata(backupFilePath, "a", "", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } _, _, err = httpd.Loaddata(backupFilePath, "", "a", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } _, _, err = httpd.Loaddata("backup.json", "1", "", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } _, _, err = httpd.Loaddata(backupFilePath+"a", "1", "", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } if runtime.GOOS != "windows" { os.Chmod(backupFilePath, 0111) _, _, err = httpd.Loaddata(backupFilePath, "1", "", http.StatusInternalServerError) if err != nil { t.Errorf("unexpected error: %v", err) } os.Chmod(backupFilePath, 0644) } // add user from backup _, _, err = httpd.Loaddata(backupFilePath, "1", "", http.StatusOK) if err != nil { t.Errorf("unexpected error: %v", err) } // update user from backup _, _, err = httpd.Loaddata(backupFilePath, "2", "", http.StatusOK) if err != nil { t.Errorf("unexpected error: %v", err) } users, _, err := httpd.GetUsers(1, 0, user.Username, http.StatusOK) if err != nil { t.Errorf("unable to get users: %v", err) } if len(users) != 1 { t.Error("Unable to get restored user") } user = users[0] _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove user: %v", err) } os.Remove(backupFilePath) createTestFile(backupFilePath, 10485761) _, _, err = httpd.Loaddata(backupFilePath, "1", "0", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } os.Remove(backupFilePath) createTestFile(backupFilePath, 65535) _, _, err = httpd.Loaddata(backupFilePath, "1", "0", http.StatusBadRequest) if err != nil { t.Errorf("unexpected error: %v", err) } os.Remove(backupFilePath) } func TestLoaddataMode(t *testing.T) { user := getTestUser() user.ID = 1 user.Username = "test_user_restore" backupData := dataprovider.BackupData{} backupData.Users = append(backupData.Users, user) backupContent, _ := json.Marshal(backupData) backupFilePath := filepath.Join(backupsPath, "backup.json") ioutil.WriteFile(backupFilePath, backupContent, 0666) _, _, err := httpd.Loaddata(backupFilePath, "0", "0", http.StatusOK) if err != nil { t.Errorf("unexpected error: %v", err) } users, _, err := httpd.GetUsers(1, 0, user.Username, http.StatusOK) if err != nil { t.Errorf("unable to get users: %v", err) } if len(users) != 1 { t.Error("Unable to get restored user") } user = users[0] oldUploadBandwidth := user.UploadBandwidth user.UploadBandwidth = oldUploadBandwidth + 128 user, _, err = httpd.UpdateUser(user, http.StatusOK) if err != nil { t.Errorf("unable to update user: %v", err) } _, _, err = httpd.Loaddata(backupFilePath, "0", "1", http.StatusOK) if err != nil { t.Errorf("unexpected error: %v", err) } users, _, err = httpd.GetUsers(1, 0, user.Username, http.StatusOK) if err != nil { t.Errorf("unable to get users: %v", err) } if len(users) != 1 { t.Error("Unable to get restored user") } user = users[0] if user.UploadBandwidth == oldUploadBandwidth { t.Error("user must not be modified") } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil { t.Errorf("unable to remove user: %v", err) } os.Remove(backupFilePath) } func TestHTTPSConnection(t *testing.T) { client := &http.Client{ Timeout: 5 * time.Second, } _, err := client.Get("https://localhost:8443" + metricsPath) if err == nil || (!strings.Contains(err.Error(), "certificate is not valid") && !strings.Contains(err.Error(), "certificate signed by unknown authority")) { t.Errorf("unexpected error: %v", err) } } // test using mock http server func TestBasicUserHandlingMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr.Code) user.MaxSessions = 10 user.UploadBandwidth = 128 user.Permissions["/"] = []string{dataprovider.PermAny, dataprovider.PermDelete, dataprovider.PermDownload} userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var updatedUser dataprovider.User err = render.DecodeJSON(rr.Body, &updatedUser) if err != nil { t.Errorf("Error decoding updated user: %v", err) } if user.MaxSessions != updatedUser.MaxSessions || user.UploadBandwidth != updatedUser.UploadBandwidth { t.Errorf("Error modifying user actual: %v, %v", updatedUser.MaxSessions, updatedUser.UploadBandwidth) } if len(updatedUser.Permissions["/"]) != 1 { t.Errorf("permissions other than any should be removed") } if !utils.IsStringInSlice(dataprovider.PermAny, updatedUser.Permissions["/"]) { t.Errorf("permissions mismatch") } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestGetUserByIdInvalidParamsMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, userPath+"/0", nil) rr := executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"/a", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } func TestAddUserNoUsernameMock(t *testing.T) { user := getTestUser() user.Username = "" userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } func TestAddUserInvalidHomeDirMock(t *testing.T) { user := getTestUser() user.HomeDir = "relative_path" userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } func TestAddUserInvalidPermsMock(t *testing.T) { user := getTestUser() user.Permissions["/"] = []string{} userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } func TestAddUserInvalidJsonMock(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer([]byte("invalid json"))) rr := executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } func TestUpdateUserMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } // permissions should not change if empty or nil permissions := user.Permissions user.Permissions = make(map[string][]string) userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var updatedUser dataprovider.User err = render.DecodeJSON(rr.Body, &updatedUser) if err != nil { t.Errorf("Error decoding updated user: %v", err) } for dir, perms := range permissions { if actualPerms, ok := updatedUser.Permissions[dir]; ok { for _, v := range actualPerms { if !utils.IsStringInSlice(v, perms) { t.Error("Permissions contents mismatch") } } } else { t.Error("Permissions directories mismatch") } } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestUserPermissionsMock(t *testing.T) { user := getTestUser() user.Permissions = make(map[string][]string) user.Permissions["/somedir"] = []string{dataprovider.PermAny} userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} user.Permissions[".."] = []string{dataprovider.PermAny} userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } user.Permissions["/somedir"] = []string{"invalid"} userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) delete(user.Permissions, "/somedir") user.Permissions["/somedir/.."] = []string{dataprovider.PermAny} userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) delete(user.Permissions, "/somedir/..") user.Permissions["not_abs_path"] = []string{dataprovider.PermAny} userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) delete(user.Permissions, "not_abs_path") user.Permissions["/somedir/../otherdir/"] = []string{dataprovider.PermListItems} userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var updatedUser dataprovider.User err = render.DecodeJSON(rr.Body, &updatedUser) if err != nil { t.Errorf("Error decoding updated user: %v", err) } if val, ok := updatedUser.Permissions["/otherdir"]; ok { if !utils.IsStringInSlice(dataprovider.PermListItems, val) { t.Error("expected permission list not found") } if len(val) != 1 { t.Errorf("Unexpected number of permissions, expected 1, actual: %v", len(val)) } } else { t.Errorf("expected dir not found in permissions") } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestUpdateUserInvalidJsonMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer([]byte("Invalid json"))) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestUpdateUserInvalidParamsMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } user.HomeDir = "" userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) userID := user.ID user.ID = 0 userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPut, userPath+"/"+strconv.FormatInt(userID, 10), bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) user.ID = userID req, _ = http.NewRequest(http.MethodPut, userPath+"/0", bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) req, _ = http.NewRequest(http.MethodPut, userPath+"/a", bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestGetUsersMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=510&offset=0&order=ASC&username="+defaultUsername, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var users []dataprovider.User err = render.DecodeJSON(rr.Body, &users) if err != nil { t.Errorf("Error decoding users: %v", err) } if len(users) != 1 { t.Errorf("1 user is expected") } req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=a&offset=0&order=ASC", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=a&order=ASC", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=0&order=ASCa", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestDeleteUserInvalidParamsMock(t *testing.T) { req, _ := http.NewRequest(http.MethodDelete, userPath+"/0", nil) rr := executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) req, _ = http.NewRequest(http.MethodDelete, userPath+"/a", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } func TestGetQuotaScansMock(t *testing.T) { req, err := http.NewRequest("GET", quotaScanPath, nil) if err != nil { t.Errorf("error get quota scan: %v", err) } rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestStartQuotaScanMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } _, err = os.Stat(user.HomeDir) if err == nil { os.Remove(user.HomeDir) } // simulate a duplicate quota scan userAsJSON = getUserAsJSON(t, user) sftpd.AddQuotaScan(user.Username) req, _ = http.NewRequest(http.MethodPost, quotaScanPath, bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusConflict, rr.Code) sftpd.RemoveQuotaScan(user.Username) userAsJSON = getUserAsJSON(t, user) req, _ = http.NewRequest(http.MethodPost, quotaScanPath, bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusCreated, rr.Code) req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var scans []sftpd.ActiveQuotaScan err = render.DecodeJSON(rr.Body, &scans) if err != nil { t.Errorf("Error get active scans: %v", err) } for len(scans) > 0 { req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = render.DecodeJSON(rr.Body, &scans) if err != nil { t.Errorf("Error get active scans: %v", err) break } time.Sleep(100 * time.Millisecond) } _, err = os.Stat(user.HomeDir) if err != nil && os.IsNotExist(err) { os.MkdirAll(user.HomeDir, 0777) } req, _ = http.NewRequest(http.MethodPost, quotaScanPath, bytes.NewBuffer(userAsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusCreated, rr.Code) req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = render.DecodeJSON(rr.Body, &scans) if err != nil { t.Errorf("Error get active scans: %v", err) } for len(scans) > 0 { req, _ = http.NewRequest(http.MethodGet, quotaScanPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = render.DecodeJSON(rr.Body, &scans) if err != nil { t.Errorf("Error get active scans: %v", err) break } time.Sleep(100 * time.Millisecond) } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) os.RemoveAll(user.GetHomeDir()) } func TestStartQuotaScanBadUserMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, quotaScanPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) } func TestStartQuotaScanNonExistentUserMock(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, quotaScanPath, bytes.NewBuffer([]byte("invalid json"))) rr := executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) } func TestGetVersionMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, versionPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestGetConnectionsMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, activeConnectionsPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestDeleteActiveConnectionMock(t *testing.T) { req, _ := http.NewRequest(http.MethodDelete, activeConnectionsPath+"/connectionID", nil) rr := executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) } func TestNotFoundMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, "/non/existing/path", nil) rr := executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) } func TestMethodNotAllowedMock(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, activeConnectionsPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusMethodNotAllowed, rr.Code) } func TestMetricsMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, metricsPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestPProfEndPointMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, pprofPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestGetWebRootMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, "/", nil) rr := executeRequest(req) checkResponseCode(t, http.StatusMovedPermanently, rr.Code) req, _ = http.NewRequest(http.MethodGet, webBasePath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusMovedPermanently, rr.Code) } func TestBasicWebUsersMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } user1 := getTestUser() user1.Username += "1" user1AsJSON := getUserAsJSON(t, user1) req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(user1AsJSON)) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = render.DecodeJSON(rr.Body, &user1) if err != nil { t.Errorf("Error get user1: %v", err) } req, _ = http.NewRequest(http.MethodGet, webUsersPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUsersPath+"?qlimit=a", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUsersPath+"?qlimit=1", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUserPath, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUserPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUserPath+"/0", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUserPath+"/a", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) form := make(url.Values) form.Set("username", user.Username) b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/0", &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusNotFound, rr.Code) req, _ = http.NewRequest(http.MethodPost, webUserPath+"/a", &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusBadRequest, rr.Code) req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user1.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestWebUserAddMock(t *testing.T) { user := getTestUser() user.UploadBandwidth = 32 user.DownloadBandwidth = 64 user.UID = 1000 mappedDir := filepath.Join(os.TempDir(), "mapped") form := make(url.Values) form.Set("username", user.Username) form.Set("home_dir", user.HomeDir) form.Set("password", user.Password) form.Set("status", strconv.Itoa(user.Status)) form.Set("expiration_date", "") form.Set("permissions", "*") form.Set("sub_dirs_permissions", " /subdir::list ,download ") form.Set("virtual_folders", fmt.Sprintf(" /vdir:: %v ", mappedDir)) form.Set("allowed_extensions", "/dir1::.jpg,.png") form.Set("denied_extensions", "/dir1::.zip") b, contentType, _ := getMultipartFormData(form, "", "") // test invalid url escape req, _ := http.NewRequest(http.MethodPost, webUserPath+"?a=%2", &b) req.Header.Set("Content-Type", contentType) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("public_keys", testPubKey) form.Set("uid", strconv.FormatInt(int64(user.UID), 10)) form.Set("gid", "a") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid gid req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("gid", "0") form.Set("max_sessions", "a") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid max sessions req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("max_sessions", "0") form.Set("quota_size", "a") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid quota size req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("quota_size", "0") form.Set("quota_files", "a") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid quota files req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("quota_files", "0") form.Set("upload_bandwidth", "a") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid upload bandwidth req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("upload_bandwidth", strconv.FormatInt(user.UploadBandwidth, 10)) form.Set("download_bandwidth", "a") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid download bandwidth req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("download_bandwidth", strconv.FormatInt(user.DownloadBandwidth, 10)) form.Set("status", "a") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid status req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("status", strconv.Itoa(user.Status)) form.Set("expiration_date", "123") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid expiration date req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("expiration_date", "") form.Set("allowed_ip", "invalid,ip") b, contentType, _ = getMultipartFormData(form, "", "") // test invalid allowed_ip req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("allowed_ip", "") form.Set("denied_ip", "192.168.1.2") // it should be 192.168.1.2/32 b, contentType, _ = getMultipartFormData(form, "", "") // test invalid denied_ip req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) form.Set("denied_ip", "") b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusSeeOther, rr.Code) // the user already exists, was created with the above request b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath, &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=0&order=ASC&username="+user.Username, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var users []dataprovider.User err := render.DecodeJSON(rr.Body, &users) if err != nil { t.Errorf("Error decoding users: %v", err) } if len(users) != 1 { t.Errorf("1 user is expected, actual: %v", len(users)) } newUser := users[0] if newUser.UID != user.UID { t.Errorf("uid does not match") } if newUser.UploadBandwidth != user.UploadBandwidth { t.Errorf("upload_bandwidth does not match") } if newUser.DownloadBandwidth != user.DownloadBandwidth { t.Errorf("download_bandwidth does not match") } if !utils.IsStringInSlice(testPubKey, newUser.PublicKeys) { t.Errorf("public_keys does not match") } if val, ok := newUser.Permissions["/subdir"]; ok { if !utils.IsStringInSlice(dataprovider.PermListItems, val) || !utils.IsStringInSlice(dataprovider.PermDownload, val) { t.Error("permssions for /subdir does not match") } } else { t.Errorf("user permissions must contain /somedir, actual: %v", newUser.Permissions) } vfolderFoumd := false for _, v := range newUser.VirtualFolders { if v.VirtualPath == "/vdir" && v.MappedPath == mappedDir { vfolderFoumd = true } } if !vfolderFoumd { t.Errorf("virtual folders must contain /vdir, actual: %+v", newUser.VirtualFolders) } extFilters := newUser.Filters.FileExtensions[0] if !utils.IsStringInSlice(".zip", extFilters.DeniedExtensions) { t.Errorf("unexpected denied extensions: %v", extFilters.DeniedExtensions) } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(newUser.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestWebUserUpdateMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } user.MaxSessions = 1 user.QuotaFiles = 2 user.QuotaSize = 3 user.GID = 1000 form := make(url.Values) form.Set("username", user.Username) form.Set("home_dir", user.HomeDir) form.Set("uid", "0") form.Set("gid", strconv.FormatInt(int64(user.GID), 10)) form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10)) form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10)) form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10)) form.Set("upload_bandwidth", "0") form.Set("download_bandwidth", "0") form.Set("permissions", "*") form.Set("sub_dirs_permissions", "/otherdir :: list ,upload ") form.Set("status", strconv.Itoa(user.Status)) form.Set("expiration_date", "2020-01-01 00:00:00") form.Set("allowed_ip", " 192.168.1.3/32, 192.168.2.0/24 ") form.Set("denied_ip", " 10.0.0.2/32 ") form.Set("denied_extensions", "/dir1::.zip") form.Set("ssh_login_methods", dataprovider.SSHLoginMethodKeyboardInteractive) b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusSeeOther, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=0&order=ASC&username="+user.Username, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var users []dataprovider.User render.DecodeJSON(rr.Body, &users) if len(users) != 1 { t.Errorf("1 user is expected") } updateUser := users[0] if user.HomeDir != updateUser.HomeDir { t.Errorf("home dir does not match") } if user.MaxSessions != updateUser.MaxSessions { t.Errorf("max_sessions does not match") } if user.QuotaFiles != updateUser.QuotaFiles { t.Errorf("quota_files does not match") } if user.QuotaSize != updateUser.QuotaSize { t.Errorf("quota_size does not match") } if user.GID != updateUser.GID { t.Errorf("gid does not match") } if val, ok := updateUser.Permissions["/otherdir"]; ok { if !utils.IsStringInSlice(dataprovider.PermListItems, val) || !utils.IsStringInSlice(dataprovider.PermUpload, val) { t.Error("permssions for /otherdir does not match") } } else { t.Errorf("user permissions must contains /otherdir, actual: %v", updateUser.Permissions) } if !utils.IsStringInSlice("192.168.1.3/32", updateUser.Filters.AllowedIP) { t.Errorf("Allowed IP/Mask does not match: %v", updateUser.Filters.AllowedIP) } if !utils.IsStringInSlice("10.0.0.2/32", updateUser.Filters.DeniedIP) { t.Errorf("Denied IP/Mask does not match: %v", updateUser.Filters.DeniedIP) } if !utils.IsStringInSlice(dataprovider.SSHLoginMethodKeyboardInteractive, updateUser.Filters.DeniedLoginMethods) { t.Errorf("Denied login methods does not match: %v", updateUser.Filters.DeniedLoginMethods) } if !utils.IsStringInSlice(".zip", updateUser.Filters.FileExtensions[0].DeniedExtensions) { t.Errorf("unexpected extensions filter: %+v", updateUser.Filters.FileExtensions) } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestWebUserS3Mock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } user.FsConfig.Provider = 1 user.FsConfig.S3Config.Bucket = "test" user.FsConfig.S3Config.Region = "eu-west-1" user.FsConfig.S3Config.AccessKey = "access-key" user.FsConfig.S3Config.AccessSecret = "access-secret" user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b" user.FsConfig.S3Config.StorageClass = "Standard" user.FsConfig.S3Config.KeyPrefix = "somedir/subdir/" user.FsConfig.S3Config.UploadPartSize = 5 user.FsConfig.S3Config.UploadConcurrency = 4 form := make(url.Values) form.Set("username", user.Username) form.Set("home_dir", user.HomeDir) form.Set("uid", "0") form.Set("gid", strconv.FormatInt(int64(user.GID), 10)) form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10)) form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10)) form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10)) form.Set("upload_bandwidth", "0") form.Set("download_bandwidth", "0") form.Set("permissions", "*") form.Set("sub_dirs_permissions", "") form.Set("status", strconv.Itoa(user.Status)) form.Set("expiration_date", "2020-01-01 00:00:00") form.Set("allowed_ip", "") form.Set("denied_ip", "") form.Set("fs_provider", "1") form.Set("s3_bucket", user.FsConfig.S3Config.Bucket) form.Set("s3_region", user.FsConfig.S3Config.Region) form.Set("s3_access_key", user.FsConfig.S3Config.AccessKey) form.Set("s3_access_secret", user.FsConfig.S3Config.AccessSecret) form.Set("s3_storage_class", user.FsConfig.S3Config.StorageClass) form.Set("s3_endpoint", user.FsConfig.S3Config.Endpoint) form.Set("s3_key_prefix", user.FsConfig.S3Config.KeyPrefix) form.Set("allowed_extensions", "/dir1::.jpg,.png") form.Set("denied_extensions", "/dir2::.zip") // test invalid s3_upload_part_size form.Set("s3_upload_part_size", "a") b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) // test invalid s3_concurrency form.Set("s3_upload_part_size", strconv.FormatInt(user.FsConfig.S3Config.UploadPartSize, 10)) form.Set("s3_upload_concurrency", "a") b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) // now add the user form.Set("s3_upload_concurrency", strconv.Itoa(user.FsConfig.S3Config.UploadConcurrency)) b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusSeeOther, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=0&order=ASC&username="+user.Username, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var users []dataprovider.User err = render.DecodeJSON(rr.Body, &users) if err != nil { t.Errorf("Error decoding users: %v", err) } if len(users) != 1 { t.Errorf("1 user is expected") } updateUser := users[0] if updateUser.ExpirationDate != 1577836800000 { t.Errorf("invalid expiration date: %v", updateUser.ExpirationDate) } if updateUser.FsConfig.S3Config.Bucket != user.FsConfig.S3Config.Bucket { t.Error("s3 bucket mismatch") } if updateUser.FsConfig.S3Config.Region != user.FsConfig.S3Config.Region { t.Error("s3 region mismatch") } if updateUser.FsConfig.S3Config.AccessKey != user.FsConfig.S3Config.AccessKey { t.Error("s3 access key mismatch") } if !strings.HasPrefix(updateUser.FsConfig.S3Config.AccessSecret, "$aes$") { t.Error("s3 access secret is not encrypted") } if updateUser.FsConfig.S3Config.StorageClass != user.FsConfig.S3Config.StorageClass { t.Error("s3 storage class mismatch") } if updateUser.FsConfig.S3Config.Endpoint != user.FsConfig.S3Config.Endpoint { t.Error("s3 endpoint mismatch") } if updateUser.FsConfig.S3Config.KeyPrefix != user.FsConfig.S3Config.KeyPrefix { t.Error("s3 key prefix mismatch") } if updateUser.FsConfig.S3Config.UploadPartSize != user.FsConfig.S3Config.UploadPartSize { t.Error("s3 upload part size mismatch") } if updateUser.FsConfig.S3Config.UploadConcurrency != user.FsConfig.S3Config.UploadConcurrency { t.Error("s3 upload concurrency mismatch") } if len(updateUser.Filters.FileExtensions) != 2 { t.Errorf("unexpected extensions filter: %+v", updateUser.Filters.FileExtensions) } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestWebUserGCSMock(t *testing.T) { user := getTestUser() userAsJSON := getUserAsJSON(t, user) req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON)) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err := render.DecodeJSON(rr.Body, &user) if err != nil { t.Errorf("Error get user: %v", err) } credentialsFilePath := filepath.Join(os.TempDir(), "gcs.json") err = createTestFile(credentialsFilePath, 0) if err != nil { t.Errorf("unable to create credential test file: %v", err) } user.FsConfig.Provider = 2 user.FsConfig.GCSConfig.Bucket = "test" user.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir/" user.FsConfig.GCSConfig.StorageClass = "standard" form := make(url.Values) form.Set("username", user.Username) form.Set("home_dir", user.HomeDir) form.Set("uid", "0") form.Set("gid", strconv.FormatInt(int64(user.GID), 10)) form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10)) form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10)) form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10)) form.Set("upload_bandwidth", "0") form.Set("download_bandwidth", "0") form.Set("permissions", "*") form.Set("sub_dirs_permissions", "") form.Set("status", strconv.Itoa(user.Status)) form.Set("expiration_date", "2020-01-01 00:00:00") form.Set("allowed_ip", "") form.Set("denied_ip", "") form.Set("fs_provider", "2") form.Set("gcs_bucket", user.FsConfig.GCSConfig.Bucket) form.Set("gcs_storage_class", user.FsConfig.GCSConfig.StorageClass) form.Set("gcs_key_prefix", user.FsConfig.GCSConfig.KeyPrefix) form.Set("allowed_extensions", "/dir1::.jpg,.png") b, contentType, _ := getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) b, contentType, _ = getMultipartFormData(form, "gcs_credential_file", credentialsFilePath) req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = createTestFile(credentialsFilePath, 4096) if err != nil { t.Errorf("unable to create credential test file: %v", err) } b, contentType, _ = getMultipartFormData(form, "gcs_credential_file", credentialsFilePath) req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusSeeOther, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=0&order=ASC&username="+user.Username, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) var users []dataprovider.User render.DecodeJSON(rr.Body, &users) if len(users) != 1 { t.Errorf("1 user is expected") } updateUser := users[0] if updateUser.ExpirationDate != 1577836800000 { t.Errorf("invalid expiration date: %v", updateUser.ExpirationDate) } if updateUser.FsConfig.Provider != user.FsConfig.Provider { t.Error("fs provider mismatch") } if updateUser.FsConfig.GCSConfig.Bucket != user.FsConfig.GCSConfig.Bucket { t.Error("GCS bucket mismatch") } if updateUser.FsConfig.GCSConfig.StorageClass != user.FsConfig.GCSConfig.StorageClass { t.Error("GCS storage class mismatch") } if updateUser.FsConfig.GCSConfig.KeyPrefix != user.FsConfig.GCSConfig.KeyPrefix { t.Error("GCS key prefix mismatch") } if updateUser.Filters.FileExtensions[0].Path != "/dir1" { t.Errorf("unexpected extensions filter: %+v", updateUser.Filters.FileExtensions) } form.Set("gcs_auto_credentials", "on") b, contentType, _ = getMultipartFormData(form, "", "") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b) req.Header.Set("Content-Type", contentType) rr = executeRequest(req) checkResponseCode(t, http.StatusSeeOther, rr.Code) req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=0&order=ASC&username="+user.Username, nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) err = render.DecodeJSON(rr.Body, &users) if err != nil { t.Errorf("Error decoding users: %v", err) } if len(users) != 1 { t.Errorf("1 user is expected") } updateUser = users[0] if updateUser.FsConfig.GCSConfig.AutomaticCredentials != 1 { t.Error("GCS automatic credentials mismatch") } req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+strconv.FormatInt(user.ID, 10), nil) rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) os.Remove(credentialsFilePath) } func TestProviderClosedMock(t *testing.T) { dataProvider := dataprovider.GetProvider() dataprovider.Close(dataProvider) req, _ := http.NewRequest(http.MethodGet, webUsersPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr.Code) req, _ = http.NewRequest(http.MethodGet, webUserPath+"/0", nil) rr = executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr.Code) form := make(url.Values) form.Set("username", "test") req, _ = http.NewRequest(http.MethodPost, webUserPath+"/0", strings.NewReader(form.Encode())) rr = executeRequest(req) checkResponseCode(t, http.StatusInternalServerError, rr.Code) config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() providerConf.CredentialsPath = credentialsPath os.RemoveAll(credentialsPath) err := dataprovider.Initialize(providerConf, configDir) if err != nil { t.Errorf("error initializing data provider: %v", err) } httpd.SetDataProvider(dataprovider.GetProvider()) sftpd.SetDataProvider(dataprovider.GetProvider()) } func TestGetWebConnectionsMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, webConnectionsPath, nil) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } func TestStaticFilesMock(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, "/static/favicon.ico", nil) rr := executeRequest(req) checkResponseCode(t, http.StatusOK, rr.Code) } 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) defer conn.Close() break } } func getTestUser() dataprovider.User { user := dataprovider.User{ Username: defaultUsername, Password: defaultPassword, HomeDir: filepath.Join(homeBasePath, defaultUsername), Status: 1, } user.Permissions = make(map[string][]string) user.Permissions["/"] = defaultPerms return user } func getUserAsJSON(t *testing.T, user dataprovider.User) []byte { json, err := json.Marshal(user) if err != nil { t.Errorf("error get user as json: %v", err) return []byte("{}") } return json } func executeRequest(req *http.Request) *httptest.ResponseRecorder { rr := httptest.NewRecorder() testServer.Config.Handler.ServeHTTP(rr, req) return rr } func checkResponseCode(t *testing.T, expected, actual int) { if expected != actual { t.Errorf("Expected response code %d. Got %d", expected, actual) } } func createTestFile(path string, size int64) error { baseDir := filepath.Dir(path) if _, err := os.Stat(baseDir); os.IsNotExist(err) { os.MkdirAll(baseDir, 0777) } content := make([]byte, size) if size > 0 { _, err := rand.Read(content) if err != nil { return err } } return ioutil.WriteFile(path, content, 0666) } func getMultipartFormData(values url.Values, fileFieldName, filePath string) (bytes.Buffer, string, error) { var b bytes.Buffer w := multipart.NewWriter(&b) for k, v := range values { for _, s := range v { if err := w.WriteField(k, s); err != nil { return b, "", err } } } if len(fileFieldName) > 0 && len(filePath) > 0 { fw, err := w.CreateFormFile(fileFieldName, filepath.Base(filePath)) if err != nil { return b, "", err } f, err := os.Open(filePath) if err != nil { return b, "", err } if _, err = io.Copy(fw, f); err != nil { return b, "", err } } err := w.Close() return b, w.FormDataContentType(), err }