add support for users' default base dir
This commit is contained in:
parent
71093bbe1b
commit
00dd5db226
10 changed files with 117 additions and 7 deletions
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -74,6 +74,7 @@ func init() {
|
|||
SSLMode: 0,
|
||||
TrackQuota: 1,
|
||||
PoolSize: 0,
|
||||
UsersBaseDir: "",
|
||||
},
|
||||
HTTPDConfig: api.HTTPDConf{
|
||||
BindPort: 8080,
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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"]
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue