add support for users' default base dir

This commit is contained in:
Nicola Murino 2019-09-28 22:48:52 +02:00
parent 71093bbe1b
commit 00dd5db226
10 changed files with 117 additions and 7 deletions

View file

@ -159,6 +159,7 @@ The `sftpgo` configuration file contains the following sections:
- 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions
- 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions. With this configuration the "quota scan" REST API can still be used to periodically update space usage for users without quota restrictions
- `pool_size`, integer. Sets the maximum number of open connections for `mysql` and `postgresql` driver. Default 0 (unlimited)
- `users_base_dir`, string. Users' default base directory. If no home dir is defined while adding a new user, and this value is a valid absolute path, then the user home dir will be automatically defined as the path obtained joining the base dir and the username
- **"httpd"**, the configuration for the HTTP server used to serve REST API
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080
- `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1"

View file

@ -37,6 +37,7 @@ const (
quotaScanPath = "/api/v1/quota_scan"
versionPath = "/api/v1/version"
metricsPath = "/metrics"
configDir = ".."
)
var (
@ -51,7 +52,6 @@ func TestMain(m *testing.M) {
} else {
homeBasePath = "/tmp"
}
configDir := ".."
logfilePath := filepath.Join(configDir, "sftpgo_api_test.log")
logger.InitLogger(logfilePath, 5, 1, 28, false, zerolog.DebugLevel)
config.LoadConfig(configDir, "")
@ -424,6 +424,71 @@ func TestCloseActiveConnection(t *testing.T) {
}
}
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")
}
api.SetDataProvider(dataprovider.GetProvider())
u := getTestUser()
u.HomeDir = ""
user, _, err := api.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 = api.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()
err = dataprovider.Initialize(providerConf, configDir)
if err != nil {
t.Errorf("error initializing data provider")
}
api.SetDataProvider(dataprovider.GetProvider())
sftpd.SetDataProvider(dataprovider.GetProvider())
}
func TestProviderErrors(t *testing.T) {
dataProvider := dataprovider.GetProvider()
dataprovider.Close(dataProvider)
_, _, err := api.GetUserByID(0, http.StatusInternalServerError)
if err != nil {
t.Errorf("get user with provider closed must fail: %v", err)
}
_, _, err = api.GetUsers(0, 0, defaultUsername, http.StatusInternalServerError)
if err != nil {
t.Errorf("get users with provider closed must fail: %v", err)
}
_, _, err = api.UpdateUser(dataprovider.User{}, http.StatusInternalServerError)
if err != nil {
t.Errorf("update user with provider closed must fail: %v", err)
}
_, err = api.RemoveUser(dataprovider.User{}, http.StatusInternalServerError)
if err != nil {
t.Errorf("delete user with provider closed must fail: %v", err)
}
config.LoadConfig(configDir, "")
providerConf := config.GetProviderConf()
err = dataprovider.Initialize(providerConf, configDir)
if err != nil {
t.Errorf("error initializing data provider")
}
api.SetDataProvider(dataprovider.GetProvider())
sftpd.SetDataProvider(dataprovider.GetProvider())
}
// test using mock http server
func TestBasicUserHandlingMock(t *testing.T) {

View file

@ -74,6 +74,7 @@ func init() {
SSLMode: 0,
TrackQuota: 1,
PoolSize: 0,
UsersBaseDir: "",
},
HTTPDConfig: api.HTTPDConf{
BindPort: 8080,

View file

@ -297,6 +297,10 @@ func (p BoltProvider) getUsers(limit int, offset int, order string, username str
return users, err
}
func (p BoltProvider) close() error {
return p.dbHandle.Close()
}
func getUserNoCredentials(user *User) User {
user.Password = ""
user.PublicKeys = []string{}

View file

@ -58,9 +58,10 @@ var (
PermCreateDirs, PermCreateSymlinks, PermOverwrite}
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
pbkdf2SHA512Prefix, sha512cryptPwdPrefix}
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix}
logSender = "dataProvider"
availabilityTicker *time.Ticker
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix}
logSender = "dataProvider"
availabilityTicker *time.Ticker
availabilityTickerDone chan bool
)
// Config provider configuration
@ -100,6 +101,11 @@ type Config struct {
// Sets the maximum number of open connections for mysql and postgresql driver.
// Default 0 (unlimited)
PoolSize int `json:"pool_size" mapstructure:"pool_size"`
// Users' default base directory.
// If no home dir is defined while adding a new user, and this value is
// a valid absolute path, then the user home dir will be automatically
// defined as the path obtained joining the base dir and the username
UsersBaseDir string `json:"users_base_dir" mapstructure:"users_base_dir"`
}
// ValidationError raised if input data is not valid
@ -151,6 +157,7 @@ type Provider interface {
getUsers(limit int, offset int, order string, username string) ([]User, error)
getUserByID(ID int64) (User, error)
checkAvailability() error
close() error
}
func init() {
@ -252,7 +259,19 @@ func GetUserByID(p Provider, ID int64) (User, error) {
return p.getUserByID(ID)
}
// Close releases all database resources
func Close(p Provider) error {
availabilityTicker.Stop()
availabilityTickerDone <- true
return p.close()
}
func validateUser(user *User) error {
if len(user.HomeDir) == 0 {
if len(config.UsersBaseDir) > 0 {
user.HomeDir = filepath.Join(config.UsersBaseDir, user.Username)
}
}
if len(user.Username) == 0 || len(user.HomeDir) == 0 {
return &ValidationError{err: "Mandatory parameters missing"}
}
@ -405,10 +424,16 @@ func getSSLMode() string {
}
func startAvailabilityTimer() {
availabilityTickerDone = make(chan bool)
checkDataprovider()
go func() {
for range availabilityTicker.C {
checkDataprovider()
for {
select {
case <-availabilityTickerDone:
return
case <-availabilityTicker.C:
checkDataprovider()
}
}
}()
}

View file

@ -87,3 +87,7 @@ func (p MySQLProvider) deleteUser(user User) error {
func (p MySQLProvider) getUsers(limit int, offset int, order string, username string) ([]User, error) {
return sqlCommonGetUsers(limit, offset, order, username, p.dbHandle)
}
func (p MySQLProvider) close() error {
return p.dbHandle.Close()
}

View file

@ -86,3 +86,7 @@ func (p PGSQLProvider) deleteUser(user User) error {
func (p PGSQLProvider) getUsers(limit int, offset int, order string, username string) ([]User, error) {
return sqlCommonGetUsers(limit, offset, order, username, p.dbHandle)
}
func (p PGSQLProvider) close() error {
return p.dbHandle.Close()
}

View file

@ -93,3 +93,7 @@ func (p SQLiteProvider) deleteUser(user User) error {
func (p SQLiteProvider) getUsers(limit int, offset int, order string, username string) ([]User, error) {
return sqlCommonGetUsers(limit, offset, order, username, p.dbHandle)
}
func (p SQLiteProvider) close() error {
return p.dbHandle.Close()
}

View file

@ -51,6 +51,7 @@ ENV SFTPGO_CONFIG_DIR=${CONFIG_DIR}
# setting SFTPGO_LOG_FILE_PATH to an empty string will log to stdout
ENV SFTPGO_LOG_FILE_PATH=${CONFIG_DIR}/sftpgo.log
ENV SFTPGO_HTTPD__BIND_ADDRESS=""
ENV SFTPGO_DATA_PROVIDER__USERS_BASE_DIR=${DATA_DIR}
ENTRYPOINT ["sftpgo"]
CMD ["serve"]

View file

@ -31,7 +31,8 @@
"users_table": "users",
"manage_users": 1,
"track_quota": 2,
"pool_size": 0
"pool_size": 0,
"users_base_dir": ""
},
"httpd": {
"bind_port": 8080,