mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 14:50:55 +03:00
REST API/Web admin: add a parameter to disconnect a user after an update
This way you can force the user to login again and so to use the updated configuration. A deleted user will be automatically disconnected. Fixes #163 Improved some docs too.
This commit is contained in:
@@ -92,6 +92,30 @@ var (
|
||||
providerDriverName string
|
||||
)
|
||||
|
||||
type fakeConnection struct {
|
||||
*common.BaseConnection
|
||||
command string
|
||||
}
|
||||
|
||||
func (c *fakeConnection) Disconnect() error {
|
||||
common.Connections.Remove(c.GetID())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeConnection) GetClientVersion() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *fakeConnection) GetCommand() string {
|
||||
return c.command
|
||||
}
|
||||
|
||||
func (c *fakeConnection) GetRemoteAddress() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *fakeConnection) SetConnDeadline() {}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
homeBasePath = os.TempDir()
|
||||
logfilePath := filepath.Join(configDir, "sftpgo_api_test.log")
|
||||
@@ -221,7 +245,7 @@ func TestBasicUserHandling(t *testing.T) {
|
||||
user.UploadBandwidth = 128
|
||||
user.DownloadBandwidth = 64
|
||||
user.ExpirationDate = utils.GetTimeAsMsSinceEpoch(time.Now())
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
users, _, err := httpd.GetUsers(0, 0, defaultUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -239,10 +263,10 @@ func TestUserStatus(t *testing.T) {
|
||||
user, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
user.Status = 2
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest)
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest, "")
|
||||
assert.NoError(t, err)
|
||||
user.Status = 1
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -602,10 +626,10 @@ func TestUserPublicKey(t *testing.T) {
|
||||
user, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
user.PublicKeys = []string{validPubKey, invalidPubKey}
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest)
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest, "")
|
||||
assert.NoError(t, err)
|
||||
user.PublicKeys = []string{validPubKey, validPubKey, validPubKey}
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -656,10 +680,16 @@ func TestUpdateUser(t *testing.T) {
|
||||
QuotaSize: 123,
|
||||
QuotaFiles: 2,
|
||||
})
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest, "invalid")
|
||||
assert.NoError(t, err)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "0")
|
||||
assert.NoError(t, err)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "1")
|
||||
assert.NoError(t, err)
|
||||
user.Permissions["/subdir"] = []string{}
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, user.Permissions["/subdir"], 0)
|
||||
assert.Len(t, user.VirtualFolders, 2)
|
||||
@@ -722,7 +752,7 @@ func TestUpdateUserQuotaUsage(t *testing.T) {
|
||||
assert.Equal(t, usedQuotaFiles, user.UsedQuotaFiles)
|
||||
assert.Equal(t, usedQuotaSize, user.UsedQuotaSize)
|
||||
user.QuotaFiles = 100
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.UpdateQuotaUsage(u, "add", http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -814,7 +844,7 @@ func TestUserFolderMapping(t *testing.T) {
|
||||
QuotaSize: 0,
|
||||
QuotaFiles: 0,
|
||||
})
|
||||
user2, _, err = httpd.UpdateUser(user2, http.StatusOK)
|
||||
user2, _, err = httpd.UpdateUser(user2, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
folders, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -839,7 +869,7 @@ func TestUserFolderMapping(t *testing.T) {
|
||||
},
|
||||
VirtualPath: "/vdir1",
|
||||
})
|
||||
user2, _, err = httpd.UpdateUser(user2, http.StatusOK)
|
||||
user2, _, err = httpd.UpdateUser(user2, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
folders, _, err = httpd.GetFolders(0, 0, mappedPath2, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -900,7 +930,7 @@ func TestUserS3Config(t *testing.T) {
|
||||
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)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -917,7 +947,7 @@ func TestUserS3Config(t *testing.T) {
|
||||
user.FsConfig.S3Config.Endpoint = "http://localhost:9000"
|
||||
user.FsConfig.S3Config.KeyPrefix = "somedir/subdir" //nolint:goconst
|
||||
user.FsConfig.S3Config.UploadConcurrency = 5
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
user.FsConfig.Provider = 0
|
||||
user.FsConfig.S3Config.Bucket = ""
|
||||
@@ -928,7 +958,7 @@ func TestUserS3Config(t *testing.T) {
|
||||
user.FsConfig.S3Config.KeyPrefix = ""
|
||||
user.FsConfig.S3Config.UploadPartSize = 0
|
||||
user.FsConfig.S3Config.UploadConcurrency = 0
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
// test user without access key and access secret (shared config state)
|
||||
user.FsConfig.Provider = 1
|
||||
@@ -940,7 +970,7 @@ func TestUserS3Config(t *testing.T) {
|
||||
user.FsConfig.S3Config.KeyPrefix = "somedir/subdir"
|
||||
user.FsConfig.S3Config.UploadPartSize = 6
|
||||
user.FsConfig.S3Config.UploadConcurrency = 4
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -956,7 +986,7 @@ func TestUserGCSConfig(t *testing.T) {
|
||||
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)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -971,7 +1001,7 @@ func TestUserGCSConfig(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
user.FsConfig.GCSConfig.Credentials = ""
|
||||
user.FsConfig.GCSConfig.AutomaticCredentials = 1
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
user.FsConfig.Provider = 1
|
||||
user.FsConfig.S3Config.Bucket = "test1"
|
||||
@@ -980,12 +1010,12 @@ func TestUserGCSConfig(t *testing.T) {
|
||||
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)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, 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)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
@@ -999,7 +1029,7 @@ func TestUpdateUserNoCredentials(t *testing.T) {
|
||||
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)
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -1009,7 +1039,7 @@ func TestUpdateUserEmptyHomeDir(t *testing.T) {
|
||||
user, _, err := httpd.AddUser(getTestUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
user.HomeDir = ""
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest)
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
@@ -1019,14 +1049,14 @@ func TestUpdateUserInvalidHomeDir(t *testing.T) {
|
||||
user, _, err := httpd.AddUser(getTestUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
user.HomeDir = "relative_path"
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest)
|
||||
_, _, err = httpd.UpdateUser(user, http.StatusBadRequest, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateNonExistentUser(t *testing.T) {
|
||||
_, _, err := httpd.UpdateUser(getTestUser(), http.StatusNotFound)
|
||||
_, _, err := httpd.UpdateUser(getTestUser(), http.StatusNotFound, "")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -1182,6 +1212,43 @@ func TestGetConnections(t *testing.T) {
|
||||
func TestCloseActiveConnection(t *testing.T) {
|
||||
_, err := httpd.CloseConnection("non_existent_id", http.StatusNotFound)
|
||||
assert.NoError(t, err)
|
||||
user := getTestUser()
|
||||
c := common.NewBaseConnection("connID", common.ProtocolSFTP, user, nil)
|
||||
fakeConn := &fakeConnection{
|
||||
BaseConnection: c,
|
||||
}
|
||||
common.Connections.Add(fakeConn)
|
||||
_, err = httpd.CloseConnection(c.GetID(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
}
|
||||
|
||||
func TestCloseConnectionAfterUserUpdateDelete(t *testing.T) {
|
||||
user, _, err := httpd.AddUser(getTestUser(), http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
c := common.NewBaseConnection("connID", common.ProtocolFTP, user, nil)
|
||||
fakeConn := &fakeConnection{
|
||||
BaseConnection: c,
|
||||
}
|
||||
common.Connections.Add(fakeConn)
|
||||
c1 := common.NewBaseConnection("connID1", common.ProtocolSFTP, user, nil)
|
||||
fakeConn1 := &fakeConnection{
|
||||
BaseConnection: c1,
|
||||
}
|
||||
common.Connections.Add(fakeConn1)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "0")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 2)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
|
||||
common.Connections.Add(fakeConn)
|
||||
common.Connections.Add(fakeConn1)
|
||||
assert.Len(t, common.Connections.GetStats(), 2)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
}
|
||||
|
||||
func TestUserBaseDir(t *testing.T) {
|
||||
@@ -1264,7 +1331,7 @@ func TestProviderErrors(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.GetUsers(1, 0, defaultUsername, http.StatusInternalServerError)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.UpdateUser(dataprovider.User{}, http.StatusInternalServerError)
|
||||
_, _, err = httpd.UpdateUser(dataprovider.User{}, http.StatusInternalServerError, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(dataprovider.User{}, http.StatusInternalServerError)
|
||||
assert.NoError(t, err)
|
||||
@@ -1508,15 +1575,31 @@ func TestLoaddataMode(t *testing.T) {
|
||||
user = users[0]
|
||||
oldUploadBandwidth := user.UploadBandwidth
|
||||
user.UploadBandwidth = oldUploadBandwidth + 128
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK)
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "0", "1", http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
c := common.NewBaseConnection("connID", common.ProtocolFTP, user, nil)
|
||||
fakeConn := &fakeConnection{
|
||||
BaseConnection: c,
|
||||
}
|
||||
common.Connections.Add(fakeConn)
|
||||
assert.Len(t, common.Connections.GetStats(), 1)
|
||||
users, _, err = httpd.GetUsers(1, 0, user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(users))
|
||||
user = users[0]
|
||||
assert.NotEqual(t, oldUploadBandwidth, user.UploadBandwidth)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "0", "2", http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
// mode 2 will update the user and close the previous connection
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
users, _, err = httpd.GetUsers(1, 0, user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(users))
|
||||
user = users[0]
|
||||
assert.Equal(t, oldUploadBandwidth, user.UploadBandwidth)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.Remove(backupFilePath)
|
||||
@@ -2430,6 +2513,7 @@ func TestWebUserUpdateMock(t *testing.T) {
|
||||
form.Set("ssh_login_methods", dataprovider.SSHLoginMethodKeyboardInteractive)
|
||||
form.Set("denied_protocols", common.ProtocolFTP)
|
||||
form.Set("max_upload_file_size", "100")
|
||||
form.Set("disconnect", "1")
|
||||
b, contentType, _ := getMultipartFormData(form, "", "")
|
||||
req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b)
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
Reference in New Issue
Block a user