mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-06 14:20:55 +03:00
REST API v2
- add JWT authentication - admins are now stored inside the data provider - admin access can be restricted based on the source IP: both proxy header and connection IP are checked - deprecate REST API CLI: it is not relevant anymore Some other changes to the REST API can still happen before releasing SFTPGo 2.0.0 Fixes #197
This commit is contained in:
49
examples/convertusers/README.md
Normal file
49
examples/convertusers/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Import users from other stores
|
||||
|
||||
`convertusers` is a very simple command line client, written in python, to import users from other stores. It requires `python3` or `python2`.
|
||||
|
||||
Here is the usage:
|
||||
|
||||
```console
|
||||
usage: convertusers [-h] [--min-uid MIN_UID] [--max-uid MAX_UID] [--usernames USERNAMES [USERNAMES ...]]
|
||||
[--force-uid FORCE_UID] [--force-gid FORCE_GID]
|
||||
input_file {unix-passwd,pure-ftpd,proftpd} output_file
|
||||
|
||||
Convert users to a JSON format suitable to use with loadddata
|
||||
|
||||
positional arguments:
|
||||
input_file
|
||||
{unix-passwd,pure-ftpd,proftpd}
|
||||
To import from unix-passwd format you need the permission to read /etc/shadow that is typically
|
||||
granted to the root user only
|
||||
output_file
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--min-uid MIN_UID if >= 0 only import users with UID greater or equal to this value. Default: -1
|
||||
--max-uid MAX_UID if >= 0 only import users with UID lesser or equal to this value. Default: -1
|
||||
--usernames USERNAMES [USERNAMES ...]
|
||||
Only import users with these usernames. Default: []
|
||||
--force-uid FORCE_UID
|
||||
if >= 0 the imported users will have this UID in SFTPGo. Default: -1
|
||||
--force-gid FORCE_GID
|
||||
if >= 0 the imported users will have this GID in SFTPGo. Default: -1
|
||||
```
|
||||
|
||||
Let's see some examples:
|
||||
|
||||
```console
|
||||
python convertusers "" unix-passwd unix_users.json --min-uid 500 --force-uid 1000 --force-gid 1000
|
||||
```
|
||||
|
||||
```console
|
||||
python convertusers pureftpd.passwd pure-ftpd pure_users.json --usernames "user1" "user2"
|
||||
```
|
||||
|
||||
```console
|
||||
python convertusers proftpd.passwd proftpd pro_users.json
|
||||
```
|
||||
|
||||
The generated json file can be used as input for the `loaddata` REST API.
|
||||
|
||||
Please note that when importing Linux/Unix users the input file is not required: `/etc/passwd` and `/etc/shadow` are automatically parsed. `/etc/shadow` read permission is typically granted to the `root` user only, so you need to execute `convertusers` as `root`.
|
||||
208
examples/convertusers/convertusers
Executable file
208
examples/convertusers/convertusers
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import pwd
|
||||
import spwd
|
||||
except ImportError:
|
||||
pwd = None
|
||||
|
||||
|
||||
class ConvertUsers:
|
||||
|
||||
def __init__(self, input_file, users_format, output_file, min_uid, max_uid, usernames, force_uid, force_gid):
|
||||
self.input_file = input_file
|
||||
self.users_format = users_format
|
||||
self.output_file = output_file
|
||||
self.min_uid = min_uid
|
||||
self.max_uid = max_uid
|
||||
self.usernames = usernames
|
||||
self.force_uid = force_uid
|
||||
self.force_gid = force_gid
|
||||
self.SFTPGoUsers = []
|
||||
|
||||
def buildUserObject(self, username, password, home_dir, uid, gid, max_sessions, quota_size, quota_files, upload_bandwidth,
|
||||
download_bandwidth, status, expiration_date, allowed_ip=[], denied_ip=[]):
|
||||
return {'id':0, 'username':username, 'password':password, 'home_dir':home_dir, 'uid':uid, 'gid':gid,
|
||||
'max_sessions':max_sessions, 'quota_size':quota_size, 'quota_files':quota_files, 'permissions':{'/':"*"},
|
||||
'upload_bandwidth':upload_bandwidth, 'download_bandwidth':download_bandwidth,
|
||||
'status':status, 'expiration_date':expiration_date,
|
||||
'filters':{'allowed_ip':allowed_ip, 'denied_ip':denied_ip}}
|
||||
|
||||
def addUser(self, user):
|
||||
user['id'] = len(self.SFTPGoUsers) + 1
|
||||
print('')
|
||||
print('New user imported: {}'.format(user))
|
||||
print('')
|
||||
self.SFTPGoUsers.append(user)
|
||||
|
||||
def saveUsers(self):
|
||||
if self.SFTPGoUsers:
|
||||
data = {'users':self.SFTPGoUsers}
|
||||
jsonData = json.dumps(data)
|
||||
with open(self.output_file, 'w') as f:
|
||||
f.write(jsonData)
|
||||
print()
|
||||
print('Number of users saved to "{}": {}. You can import them using loaddata'.format(self.output_file,
|
||||
len(self.SFTPGoUsers)))
|
||||
print()
|
||||
sys.exit(0)
|
||||
else:
|
||||
print('No user imported')
|
||||
sys.exit(1)
|
||||
|
||||
def convert(self):
|
||||
if self.users_format == 'unix-passwd':
|
||||
self.convertFromUnixPasswd()
|
||||
elif self.users_format == 'pure-ftpd':
|
||||
self.convertFromPureFTPD()
|
||||
else:
|
||||
self.convertFromProFTPD()
|
||||
self.saveUsers()
|
||||
|
||||
def isUserValid(self, username, uid):
|
||||
if self.usernames and not username in self.usernames:
|
||||
return False
|
||||
if self.min_uid >= 0 and uid < self.min_uid:
|
||||
return False
|
||||
if self.max_uid >= 0 and uid > self.max_uid:
|
||||
return False
|
||||
return True
|
||||
|
||||
def convertFromUnixPasswd(self):
|
||||
days_from_epoch_time = time.time() / 86400
|
||||
for user in pwd.getpwall():
|
||||
username = user.pw_name
|
||||
password = user.pw_passwd
|
||||
uid = user.pw_uid
|
||||
gid = user.pw_gid
|
||||
home_dir = user.pw_dir
|
||||
status = 1
|
||||
expiration_date = 0
|
||||
if not self.isUserValid(username, uid):
|
||||
continue
|
||||
if self.force_uid >= 0:
|
||||
uid = self.force_uid
|
||||
if self.force_gid >= 0:
|
||||
gid = self.force_gid
|
||||
# FIXME: if the passwords aren't in /etc/shadow they are probably DES encrypted and we don't support them
|
||||
if password == 'x' or password == '*':
|
||||
user_info = spwd.getspnam(username)
|
||||
password = user_info.sp_pwdp
|
||||
if not password or password == '!!' or password == '!*':
|
||||
print('cannot import user "{}" without a password'.format(username))
|
||||
continue
|
||||
if user_info.sp_inact > 0:
|
||||
last_pwd_change_diff = days_from_epoch_time - user_info.sp_lstchg
|
||||
if last_pwd_change_diff > user_info.sp_inact:
|
||||
status = 0
|
||||
if user_info.sp_expire > 0:
|
||||
expiration_date = user_info.sp_expire * 86400
|
||||
self.addUser(self.buildUserObject(username, password, home_dir, uid, gid, 0, 0, 0, 0, 0, status,
|
||||
expiration_date))
|
||||
|
||||
def convertFromProFTPD(self):
|
||||
with open(self.input_file, 'r') as f:
|
||||
for line in f:
|
||||
fields = line.split(':')
|
||||
if len(fields) > 6:
|
||||
username = fields[0]
|
||||
password = fields[1]
|
||||
uid = int(fields[2])
|
||||
gid = int(fields[3])
|
||||
home_dir = fields[5]
|
||||
if not self.isUserValid(username, uid):
|
||||
continue
|
||||
if self.force_uid >= 0:
|
||||
uid = self.force_uid
|
||||
if self.force_gid >= 0:
|
||||
gid = self.force_gid
|
||||
self.addUser(self.buildUserObject(username, password, home_dir, uid, gid, 0, 0, 0, 0, 0, 1, 0))
|
||||
|
||||
def convertPureFTPDIP(self, fields):
|
||||
result = []
|
||||
if not fields:
|
||||
return result
|
||||
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))
|
||||
continue
|
||||
if '/' not in ip_mask:
|
||||
ip_mask += '/32'
|
||||
result.append(ip_mask)
|
||||
return result
|
||||
|
||||
def convertFromPureFTPD(self):
|
||||
with open(self.input_file, 'r') as f:
|
||||
for line in f:
|
||||
fields = line.split(':')
|
||||
if len(fields) > 16:
|
||||
username = fields[0]
|
||||
password = fields[1]
|
||||
uid = int(fields[2])
|
||||
gid = int(fields[3])
|
||||
home_dir = fields[5]
|
||||
upload_bandwidth = 0
|
||||
if fields[6]:
|
||||
upload_bandwidth = int(int(fields[6]) / 1024)
|
||||
download_bandwidth = 0
|
||||
if fields[7]:
|
||||
download_bandwidth = int(int(fields[7]) / 1024)
|
||||
max_sessions = 0
|
||||
if fields[10]:
|
||||
max_sessions = int(fields[10])
|
||||
quota_files = 0
|
||||
if fields[11]:
|
||||
quota_files = int(fields[11])
|
||||
quota_size = 0
|
||||
if fields[12]:
|
||||
quota_size = int(fields[12])
|
||||
allowed_ip = self.convertPureFTPDIP(fields[15])
|
||||
denied_ip = self.convertPureFTPDIP(fields[16])
|
||||
if not self.isUserValid(username, uid):
|
||||
continue
|
||||
if self.force_uid >= 0:
|
||||
uid = self.force_uid
|
||||
if self.force_gid >= 0:
|
||||
gid = self.force_gid
|
||||
self.addUser(self.buildUserObject(username, password, home_dir, uid, gid, max_sessions, quota_size,
|
||||
quota_files, upload_bandwidth, download_bandwidth, 1, 0, allowed_ip,
|
||||
denied_ip))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=
|
||||
'Convert users to a JSON format suitable to use with loadddata')
|
||||
supportedUsersFormats = []
|
||||
help_text = ''
|
||||
if pwd is not None:
|
||||
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')
|
||||
parser.add_argument('input_file', type=str)
|
||||
parser.add_argument('users_format', type=str, choices=supportedUsersFormats, help=help_text)
|
||||
parser.add_argument('output_file', type=str)
|
||||
parser.add_argument('--min-uid', type=int, default=-1, help='if >= 0 only import users with UID greater or equal ' +
|
||||
'to this value. Default: %(default)s')
|
||||
parser.add_argument('--max-uid', type=int, default=-1, help='if >= 0 only import users with UID lesser or equal ' +
|
||||
'to this value. Default: %(default)s')
|
||||
parser.add_argument('--usernames', type=str, nargs='+', default=[], help='Only import users with these usernames. ' +
|
||||
'Default: %(default)s')
|
||||
parser.add_argument('--force-uid', type=int, default=-1, help='if >= 0 the imported users will have this UID in ' +
|
||||
'SFTPGo. Default: %(default)s')
|
||||
parser.add_argument('--force-gid', type=int, default=-1, help='if >= 0 the imported users will have this GID in ' +
|
||||
'SFTPGo. Default: %(default)s')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
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)
|
||||
convertUsers.convert()
|
||||
@@ -1,5 +1,7 @@
|
||||
# REST API CLI client
|
||||
|
||||
:warning: This sample client is deprecated and it will work only with api V1 (SFTPGo <= 1.2.2). You can easily build your own client from the OpenAPI schema.
|
||||
|
||||
`sftpgo_api_cli` is a very simple command line client for `SFTPGo` REST API written in python.
|
||||
|
||||
It has the following requirements:
|
||||
|
||||
Reference in New Issue
Block a user