loaddata: add an option that allows to not modify existing users
This commit is contained in:
parent
3491717c26
commit
e647f3626e
6 changed files with 170 additions and 80 deletions
|
@ -59,19 +59,10 @@ func dumpData(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func loadData(w http.ResponseWriter, r *http.Request) {
|
||||
var inputFile string
|
||||
var err error
|
||||
scanQuota := 0
|
||||
if _, ok := r.URL.Query()["input_file"]; ok {
|
||||
inputFile = strings.TrimSpace(r.URL.Query().Get("input_file"))
|
||||
}
|
||||
if _, ok := r.URL.Query()["scan_quota"]; ok {
|
||||
scanQuota, err = strconv.Atoi(r.URL.Query().Get("scan_quota"))
|
||||
if err != nil {
|
||||
err = errors.New("Invalid scan_quota")
|
||||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
inputFile, scanQuota, mode, err := getLoaddataOptions(r)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !filepath.IsAbs(inputFile) {
|
||||
sendAPIResponse(w, r, fmt.Errorf("Invalid input_file %#v: it must be an absolute path", inputFile), "", http.StatusBadRequest)
|
||||
|
@ -103,6 +94,10 @@ func loadData(w http.ResponseWriter, r *http.Request) {
|
|||
for _, user := range dump.Users {
|
||||
u, err := dataprovider.UserExists(dataProvider, user.Username)
|
||||
if err == nil {
|
||||
if mode == 1 {
|
||||
logger.Debug(logSender, "", "loaddata mode = 1 existing user: %#v not updated", u.Username)
|
||||
continue
|
||||
}
|
||||
user.ID = u.ID
|
||||
user.LastLogin = u.LastLogin
|
||||
user.UsedQuotaSize = u.UsedQuotaSize
|
||||
|
@ -136,3 +131,26 @@ func loadData(w http.ResponseWriter, r *http.Request) {
|
|||
func needQuotaScan(scanQuota int, user *dataprovider.User) bool {
|
||||
return scanQuota == 1 || (scanQuota == 2 && user.HasQuotaRestrictions())
|
||||
}
|
||||
|
||||
func getLoaddataOptions(r *http.Request) (string, int, int, error) {
|
||||
var inputFile string
|
||||
var err error
|
||||
scanQuota := 0
|
||||
restoreMode := 0
|
||||
if _, ok := r.URL.Query()["input_file"]; ok {
|
||||
inputFile = strings.TrimSpace(r.URL.Query().Get("input_file"))
|
||||
}
|
||||
if _, ok := r.URL.Query()["scan_quota"]; ok {
|
||||
scanQuota, err = strconv.Atoi(r.URL.Query().Get("scan_quota"))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid scan_quota: %v", err)
|
||||
}
|
||||
}
|
||||
if _, ok := r.URL.Query()["mode"]; ok {
|
||||
restoreMode, err = strconv.Atoi(r.URL.Query().Get("mode"))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid mode: %v", err)
|
||||
}
|
||||
}
|
||||
return inputFile, scanQuota, restoreMode, err
|
||||
}
|
||||
|
|
|
@ -338,7 +338,7 @@ func Dumpdata(outputFile string, expectedStatusCode int) (map[string]interface{}
|
|||
// Loaddata restores a backup.
|
||||
// New users are added, existing users are updated. Users will be restored one by one and the restore is stopped if a
|
||||
// user cannot be added/updated, so it could happen a partial restore
|
||||
func Loaddata(inputFile, scanQuota string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
|
||||
func Loaddata(inputFile, scanQuota, mode string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
|
||||
var response map[string]interface{}
|
||||
var body []byte
|
||||
url, err := url.Parse(buildURLRelativeToBase(loadDataPath))
|
||||
|
@ -350,6 +350,9 @@ func Loaddata(inputFile, scanQuota string, expectedStatusCode int) (map[string]i
|
|||
if len(scanQuota) > 0 {
|
||||
q.Add("scan_quota", scanQuota)
|
||||
}
|
||||
if len(mode) > 0 {
|
||||
q.Add("mode", mode)
|
||||
}
|
||||
url.RawQuery = q.Encode()
|
||||
resp, err := getHTTPClient().Get(url.String())
|
||||
if err != nil {
|
||||
|
|
|
@ -727,7 +727,7 @@ func TestProviderErrors(t *testing.T) {
|
|||
backupContent, _ := json.Marshal(backupData)
|
||||
backupFilePath := filepath.Join(backupsPath, "backup.json")
|
||||
ioutil.WriteFile(backupFilePath, backupContent, 0666)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "", http.StatusInternalServerError)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
|
||||
if err != nil {
|
||||
t.Errorf("get provider status with provider closed must fail: %v", err)
|
||||
}
|
||||
|
@ -800,33 +800,37 @@ func TestLoaddata(t *testing.T) {
|
|||
backupContent, _ := json.Marshal(backupData)
|
||||
backupFilePath := filepath.Join(backupsPath, "backup.json")
|
||||
ioutil.WriteFile(backupFilePath, backupContent, 0666)
|
||||
_, _, err := httpd.Loaddata(backupFilePath, "a", http.StatusBadRequest)
|
||||
_, _, err := httpd.Loaddata(backupFilePath, "a", "", http.StatusBadRequest)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
_, _, err = httpd.Loaddata("backup.json", "1", http.StatusBadRequest)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "", "a", http.StatusBadRequest)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
_, _, err = httpd.Loaddata(backupFilePath+"a", "1", http.StatusBadRequest)
|
||||
_, _, 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)
|
||||
_, _, 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)
|
||||
_, _, 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)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "2", "", http.StatusOK)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
@ -844,19 +848,68 @@ func TestLoaddata(t *testing.T) {
|
|||
}
|
||||
os.Remove(backupFilePath)
|
||||
createTestFile(backupFilePath, 10485761)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "1", http.StatusBadRequest)
|
||||
_, _, 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", http.StatusBadRequest)
|
||||
_, _, 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 := httpd.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)
|
||||
}
|
||||
|
||||
// test using mock http server
|
||||
|
||||
func TestBasicUserHandlingMock(t *testing.T) {
|
||||
|
|
|
@ -340,7 +340,7 @@ func TestApiCallsWithBadURL(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("request with invalid URL must fail")
|
||||
}
|
||||
_, _, err = Loaddata("/tmp/backup.json", "", http.StatusBadRequest)
|
||||
_, _, err = Loaddata("/tmp/backup.json", "", "", http.StatusBadRequest)
|
||||
if err == nil {
|
||||
t.Error("request with invalid URL must fail")
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ func TestApiCallToNotListeningServer(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Errorf("request to an inactive URL must fail")
|
||||
}
|
||||
_, _, err = Loaddata("/tmp/backup.json", "", http.StatusOK)
|
||||
_, _, err = Loaddata("/tmp/backup.json", "", "", http.StatusOK)
|
||||
if err == nil {
|
||||
t.Errorf("request to an inactive URL must fail")
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ openapi: 3.0.1
|
|||
info:
|
||||
title: SFTPGo
|
||||
description: 'SFTPGo REST API'
|
||||
version: 1.6.0
|
||||
version: 1.6.1
|
||||
|
||||
servers:
|
||||
- url: /api/v1
|
||||
|
@ -589,7 +589,7 @@ paths:
|
|||
tags:
|
||||
- maintenance
|
||||
summary: Restore SFTPGo data from a JSON backup
|
||||
description: New users are added, existing users are updated. Users will be restored one by one and the restore is stopped if a user cannot be added/updated, so it could happen a partial restore
|
||||
description: Users will be restored one by one and the restore is stopped if a user cannot be added or updated, so it could happen a partial restore
|
||||
operationId: loaddata
|
||||
parameters:
|
||||
- in: query
|
||||
|
@ -608,10 +608,21 @@ paths:
|
|||
- 2
|
||||
description: >
|
||||
Quota scan:
|
||||
* `0` no quota scan is done, the imported user will have used_quota_size and used_quota_file = 0
|
||||
* `0` no quota scan is done, the imported user will have used_quota_size and used_quota_file = 0. This is the default
|
||||
* `1` scan quota
|
||||
* `2` scan quota if the user has quota restrictions
|
||||
required: false
|
||||
- in: query
|
||||
name: mode
|
||||
schema:
|
||||
type: integer
|
||||
enum:
|
||||
- 0
|
||||
- 1
|
||||
description: >
|
||||
Mode:
|
||||
* `0` New users are added, existing users are updated. This is the default
|
||||
* `1` New users are added, existing users are not modified
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
|
|
|
@ -50,50 +50,50 @@ class SFTPGoApiRequests:
|
|||
|
||||
def formatAsJSON(self, text):
|
||||
if not text:
|
||||
return ""
|
||||
return ''
|
||||
json_string = json.dumps(json.loads(text), sort_keys=True, indent=2)
|
||||
if not self.no_color and pygments:
|
||||
return pygments.highlight(json_string, JsonLexer(), TerminalFormatter())
|
||||
return json_string
|
||||
|
||||
def printResponse(self, r):
|
||||
if "content-type" in r.headers and "application/json" in r.headers["content-type"]:
|
||||
if 'content-type' in r.headers and 'application/json' in r.headers['content-type']:
|
||||
if self.debug:
|
||||
if pygments is None:
|
||||
print('')
|
||||
print('Response color highlight is not available: you need pygments 1.5 or above.')
|
||||
print('')
|
||||
print("Executed request: {} {} - request body: {}".format(
|
||||
print('Executed request: {} {} - request body: {}'.format(
|
||||
r.request.method, r.url, self.formatAsJSON(r.request.body)))
|
||||
print('')
|
||||
print("Got response, status code: {} body:".format(r.status_code))
|
||||
print('Got response, status code: {} body:'.format(r.status_code))
|
||||
print(self.formatAsJSON(r.text))
|
||||
else:
|
||||
print(r.text)
|
||||
|
||||
def buildUserObject(self, user_id=0, username="", password="", public_keys=[], home_dir="", uid=0, gid=0,
|
||||
def buildUserObject(self, user_id=0, username='', password='', public_keys=[], home_dir='', uid=0, gid=0,
|
||||
max_sessions=0, quota_size=0, quota_files=0, permissions={}, upload_bandwidth=0, download_bandwidth=0,
|
||||
status=1, expiration_date=0, allowed_ip=[], denied_ip=[], fs_provider='local', s3_bucket='',
|
||||
s3_region='', s3_access_key='', s3_access_secret='', s3_endpoint='', s3_storage_class='',
|
||||
s3_key_prefix='', gcs_bucket='', gcs_key_prefix='', gcs_storage_class='', gcs_credentials_file=''):
|
||||
user = {"id":user_id, "username":username, "uid":uid, "gid":gid,
|
||||
"max_sessions":max_sessions, "quota_size":quota_size, "quota_files":quota_files,
|
||||
"upload_bandwidth":upload_bandwidth, "download_bandwidth":download_bandwidth,
|
||||
"status":status, "expiration_date":expiration_date}
|
||||
user = {'id':user_id, 'username':username, 'uid':uid, 'gid':gid,
|
||||
'max_sessions':max_sessions, 'quota_size':quota_size, 'quota_files':quota_files,
|
||||
'upload_bandwidth':upload_bandwidth, 'download_bandwidth':download_bandwidth,
|
||||
'status':status, 'expiration_date':expiration_date}
|
||||
if password is not None:
|
||||
user.update({"password":password})
|
||||
user.update({'password':password})
|
||||
if public_keys:
|
||||
if len(public_keys) == 1 and not public_keys[0]:
|
||||
user.update({"public_keys":[]})
|
||||
user.update({'public_keys':[]})
|
||||
else:
|
||||
user.update({"public_keys":public_keys})
|
||||
user.update({'public_keys':public_keys})
|
||||
if home_dir:
|
||||
user.update({"home_dir":home_dir})
|
||||
user.update({'home_dir':home_dir})
|
||||
if permissions:
|
||||
user.update({"permissions":permissions})
|
||||
user.update({'permissions':permissions})
|
||||
if allowed_ip or denied_ip:
|
||||
user.update({"filters":self.buildFilters(allowed_ip, denied_ip)})
|
||||
user.update({"filesystem":self.buildFsConfig(fs_provider, s3_bucket, s3_region, s3_access_key, s3_access_secret,
|
||||
user.update({'filters':self.buildFilters(allowed_ip, denied_ip)})
|
||||
user.update({'filesystem':self.buildFsConfig(fs_provider, s3_bucket, s3_region, s3_access_key, s3_access_secret,
|
||||
s3_endpoint, s3_storage_class, s3_key_prefix, gcs_bucket,
|
||||
gcs_key_prefix, gcs_storage_class, gcs_credentials_file)})
|
||||
return user
|
||||
|
@ -101,16 +101,16 @@ class SFTPGoApiRequests:
|
|||
def buildPermissions(self, root_perms, subdirs_perms):
|
||||
permissions = {}
|
||||
if root_perms:
|
||||
permissions.update({"/":root_perms})
|
||||
permissions.update({'/':root_perms})
|
||||
for p in subdirs_perms:
|
||||
if ":" in p:
|
||||
if ':' in p:
|
||||
directory = None
|
||||
values = []
|
||||
for value in p.split(":"):
|
||||
for value in p.split(':'):
|
||||
if directory is None:
|
||||
directory = value
|
||||
else:
|
||||
values = [v.strip() for v in value.split(",") if v.strip()]
|
||||
values = [v.strip() for v in value.split(',') if v.strip()]
|
||||
if directory and values:
|
||||
permissions.update({directory:values})
|
||||
return permissions
|
||||
|
@ -145,16 +145,16 @@ class SFTPGoApiRequests:
|
|||
fs_config.update({'provider':2, 'gcsconfig':gcsconfig})
|
||||
return fs_config
|
||||
|
||||
def getUsers(self, limit=100, offset=0, order="ASC", username=""):
|
||||
r = requests.get(self.userPath, params={"limit":limit, "offset":offset, "order":order,
|
||||
"username":username}, auth=self.auth, verify=self.verify)
|
||||
def getUsers(self, limit=100, offset=0, order='ASC', username=''):
|
||||
r = requests.get(self.userPath, params={'limit':limit, 'offset':offset, 'order':order,
|
||||
'username':username}, auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
def getUserByID(self, user_id):
|
||||
r = requests.get(urlparse.urljoin(self.userPath, "user/" + str(user_id)), auth=self.auth, verify=self.verify)
|
||||
r = requests.get(urlparse.urljoin(self.userPath, 'user/' + str(user_id)), auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
def addUser(self, username="", password="", public_keys="", home_dir="", uid=0, gid=0, max_sessions=0, quota_size=0,
|
||||
def addUser(self, username='', password='', public_keys='', home_dir='', uid=0, gid=0, max_sessions=0, quota_size=0,
|
||||
quota_files=0, perms=[], upload_bandwidth=0, download_bandwidth=0, status=1, expiration_date=0,
|
||||
subdirs_permissions=[], allowed_ip=[], denied_ip=[], fs_provider='local', s3_bucket='', s3_region='',
|
||||
s3_access_key='', s3_access_secret='', s3_endpoint='', s3_storage_class='', s3_key_prefix='', gcs_bucket='',
|
||||
|
@ -167,7 +167,7 @@ class SFTPGoApiRequests:
|
|||
r = requests.post(self.userPath, json=u, auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
def updateUser(self, user_id, username="", password="", public_keys="", home_dir="", uid=0, gid=0, max_sessions=0,
|
||||
def updateUser(self, user_id, username='', password='', public_keys='', home_dir='', uid=0, gid=0, max_sessions=0,
|
||||
quota_size=0, quota_files=0, perms=[], upload_bandwidth=0, download_bandwidth=0, status=1,
|
||||
expiration_date=0, subdirs_permissions=[], allowed_ip=[], denied_ip=[], fs_provider='local',
|
||||
s3_bucket='', s3_region='', s3_access_key='', s3_access_secret='', s3_endpoint='', s3_storage_class='',
|
||||
|
@ -177,11 +177,11 @@ class SFTPGoApiRequests:
|
|||
status, expiration_date, allowed_ip, denied_ip, fs_provider, s3_bucket, s3_region, s3_access_key,
|
||||
s3_access_secret, s3_endpoint, s3_storage_class, s3_key_prefix, gcs_bucket, gcs_key_prefix, gcs_storage_class,
|
||||
gcs_credentials_file)
|
||||
r = requests.put(urlparse.urljoin(self.userPath, "user/" + str(user_id)), json=u, auth=self.auth, verify=self.verify)
|
||||
r = requests.put(urlparse.urljoin(self.userPath, 'user/' + str(user_id)), json=u, auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
def deleteUser(self, user_id):
|
||||
r = requests.delete(urlparse.urljoin(self.userPath, "user/" + str(user_id)), auth=self.auth, verify=self.verify)
|
||||
r = requests.delete(urlparse.urljoin(self.userPath, 'user/' + str(user_id)), auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
def getConnections(self):
|
||||
|
@ -189,7 +189,7 @@ class SFTPGoApiRequests:
|
|||
self.printResponse(r)
|
||||
|
||||
def closeConnection(self, connectionID):
|
||||
r = requests.delete(urlparse.urljoin(self.activeConnectionsPath, "connection/" + str(connectionID)), auth=self.auth)
|
||||
r = requests.delete(urlparse.urljoin(self.activeConnectionsPath, 'connection/' + str(connectionID)), auth=self.auth)
|
||||
self.printResponse(r)
|
||||
|
||||
def getQuotaScans(self):
|
||||
|
@ -210,12 +210,13 @@ class SFTPGoApiRequests:
|
|||
self.printResponse(r)
|
||||
|
||||
def dumpData(self, output_file):
|
||||
r = requests.get(self.dumpDataPath, params={"output_file":output_file}, auth=self.auth,
|
||||
r = requests.get(self.dumpDataPath, params={'output_file':output_file}, auth=self.auth,
|
||||
verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
def loadData(self, input_file, scan_quota):
|
||||
r = requests.get(self.loadDataPath, params={"input_file":input_file, "scan_quota":scan_quota},
|
||||
def loadData(self, input_file, scan_quota, mode):
|
||||
r = requests.get(self.loadDataPath, params={'input_file':input_file, 'scan_quota':scan_quota,
|
||||
'mode':mode},
|
||||
auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
|
@ -237,7 +238,7 @@ class ConvertUsers:
|
|||
self.SFTPGoRestAPI = api
|
||||
|
||||
def addUser(self, user):
|
||||
user["id"] = len(self.SFTPGoUsers) + 1
|
||||
user['id'] = len(self.SFTPGoUsers) + 1
|
||||
print('')
|
||||
print('New user imported: {}'.format(user))
|
||||
print('')
|
||||
|
@ -245,7 +246,7 @@ class ConvertUsers:
|
|||
|
||||
def saveUsers(self):
|
||||
if self.SFTPGoUsers:
|
||||
data = {"users":self.SFTPGoUsers}
|
||||
data = {'users':self.SFTPGoUsers}
|
||||
jsonData = json.dumps(data)
|
||||
with open(self.output_file, 'w') as f:
|
||||
f.write(jsonData)
|
||||
|
@ -259,9 +260,9 @@ class ConvertUsers:
|
|||
sys.exit(1)
|
||||
|
||||
def convert(self):
|
||||
if self.users_format == "unix-passwd":
|
||||
if self.users_format == 'unix-passwd':
|
||||
self.convertFromUnixPasswd()
|
||||
elif self.users_format == "pure-ftpd":
|
||||
elif self.users_format == 'pure-ftpd':
|
||||
self.convertFromPureFTPD()
|
||||
else:
|
||||
self.convertFromProFTPD()
|
||||
|
@ -333,15 +334,15 @@ class ConvertUsers:
|
|||
result = []
|
||||
if not fields:
|
||||
return result
|
||||
for v in fields.split(","):
|
||||
for v in fields.split(','):
|
||||
ip_mask = v.strip()
|
||||
if not ip_mask:
|
||||
continue
|
||||
if ip_mask.count(".") < 3 and ip_mask.count(":") < 3:
|
||||
print("cannot import pure-ftpd IP: {}".format(ip_mask))
|
||||
if ip_mask.count('.') < 3 and ip_mask.count(':') < 3:
|
||||
print('cannot import pure-ftpd IP: {}'.format(ip_mask))
|
||||
continue
|
||||
if "/" not in ip_mask:
|
||||
ip_mask += "/32"
|
||||
if '/' not in ip_mask:
|
||||
ip_mask += '/32'
|
||||
result.append(ip_mask)
|
||||
return result
|
||||
|
||||
|
@ -389,9 +390,9 @@ def validDate(s):
|
|||
if not s:
|
||||
return datetime.fromtimestamp(0)
|
||||
try:
|
||||
return datetime.strptime(s, "%Y-%m-%d")
|
||||
return datetime.strptime(s, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
msg = "Not a valid date: '{0}'.".format(s)
|
||||
msg = 'Not a valid date: "{0}".'.format(s)
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
|
||||
|
||||
|
@ -411,7 +412,7 @@ def addCommonUserArguments(parser):
|
|||
help='Maximum concurrent sessions. 0 means unlimited. Default: %(default)s')
|
||||
parser.add_argument('-S', '--quota-size', type=int, default=0,
|
||||
help='Maximum size allowed as bytes. 0 means unlimited. Default: %(default)s')
|
||||
parser.add_argument('-F', '--quota-files', type=int, default=0, help="default: %(default)s")
|
||||
parser.add_argument('-F', '--quota-files', type=int, default=0, help='default: %(default)s')
|
||||
parser.add_argument('-G', '--permissions', type=str, nargs='+', default=[],
|
||||
choices=['*', 'list', 'download', 'upload', 'overwrite', 'delete', 'rename', 'create_dirs',
|
||||
'create_symlinks', 'chmod', 'chown', 'chtimes'], help='Permissions for the root directory '
|
||||
|
@ -424,7 +425,7 @@ def addCommonUserArguments(parser):
|
|||
help='Maximum download bandwidth as KB/s, 0 means unlimited. Default: %(default)s')
|
||||
parser.add_argument('--status', type=int, choices=[0, 1], default=1,
|
||||
help='User\'s status. 1 enabled, 0 disabled. Default: %(default)s')
|
||||
parser.add_argument('-E', '--expiration-date', type=validDate, default="",
|
||||
parser.add_argument('-E', '--expiration-date', type=validDate, default='',
|
||||
help='Expiration date as YYYY-MM-DD, empty string means no expiration. Default: %(default)s')
|
||||
parser.add_argument('-Y', '--allowed-ip', type=str, nargs='+', default=[],
|
||||
help='Allowed IP/Mask in CIDR notation. For example "192.168.2.0/24" or "2001:db8::/32". Default: %(default)s')
|
||||
|
@ -455,7 +456,7 @@ if __name__ == '__main__':
|
|||
help='Base URL for SFTPGo REST API. Default: %(default)s')
|
||||
parser.add_argument('-a', '--auth-type', type=str, default=None, choices=['basic', 'digest'],
|
||||
help='HTTP authentication type. Default: %(default)s')
|
||||
parser.add_argument("-u", "--auth-user", type=str, default="",
|
||||
parser.add_argument('-u', '--auth-user', type=str, default='',
|
||||
help='User for HTTP authentication. Default: %(default)s')
|
||||
parser.add_argument('-p', '--auth-password', type=str, default='',
|
||||
help='Password for HTTP authentication. Default: %(default)s')
|
||||
|
@ -464,7 +465,7 @@ if __name__ == '__main__':
|
|||
parser.add_argument('-i', '--insecure', dest='secure', action='store_false',
|
||||
help='Set to false to ignore verifying the SSL certificate')
|
||||
parser.set_defaults(secure=True)
|
||||
has_colors_default = pygments is not None and platform.system() != "Windows"
|
||||
has_colors_default = pygments is not None and platform.system() != 'Windows'
|
||||
group = parser.add_mutually_exclusive_group(required=False)
|
||||
group.add_argument('-t', '--no-color', dest='no_color', action='store_true', default=(not has_colors_default),
|
||||
help='Disable color highlight for JSON responses. You need python pygments module 1.5 or above to have highlighted output')
|
||||
|
@ -519,15 +520,19 @@ if __name__ == '__main__':
|
|||
parserLoadData.add_argument('-Q', '--scan-quota', type=int, choices=[0, 1, 2], default=0,
|
||||
help='0 means no quota scan after a user is added/updated. 1 means always scan quota. 2 ' +
|
||||
'means scan quota if the user has quota restrictions. Default: %(default)s')
|
||||
parserLoadData.add_argument('-M', '--mode', type=int, choices=[0, 1], default=0,
|
||||
help='0 means new users are added, existing users are updated. 1 means new users are added,' +
|
||||
' existing users are not modified. Default: %(default)s')
|
||||
|
||||
parserConvertUsers = subparsers.add_parser('convert-users', help='Convert users to a JSON format suitable to use with loadddata')
|
||||
parserConvertUsers = subparsers.add_parser('convert-users', help='Convert users to a JSON format suitable to use ' +
|
||||
'with loadddata')
|
||||
supportedUsersFormats = []
|
||||
help_text = ''
|
||||
if pwd is not None:
|
||||
supportedUsersFormats.append("unix-passwd")
|
||||
supportedUsersFormats.append('unix-passwd')
|
||||
help_text = 'To import from unix-passwd format you need the permission to read /etc/shadow that is typically granted to the root user only'
|
||||
supportedUsersFormats.append("pure-ftpd")
|
||||
supportedUsersFormats.append("proftpd")
|
||||
supportedUsersFormats.append('pure-ftpd')
|
||||
supportedUsersFormats.append('proftpd')
|
||||
parserConvertUsers.add_argument('input_file', type=str)
|
||||
parserConvertUsers.add_argument('users_format', type=str, choices=supportedUsersFormats, help=help_text)
|
||||
parserConvertUsers.add_argument('output_file', type=str)
|
||||
|
@ -581,7 +586,7 @@ if __name__ == '__main__':
|
|||
elif args.command == 'dumpdata':
|
||||
api.dumpData(args.output_file)
|
||||
elif args.command == 'loaddata':
|
||||
api.loadData(args.input_file, args.scan_quota)
|
||||
api.loadData(args.input_file, args.scan_quota, args.mode)
|
||||
elif args.command == 'convert-users':
|
||||
convertUsers = ConvertUsers(args.input_file, args.users_format, args.output_file, args.min_uid, args.max_uid,
|
||||
args.usernames, args.force_uid, args.force_gid)
|
||||
|
|
Loading…
Reference in a new issue