parent
2054dfd83d
commit
4bb9d07dde
15 changed files with 128 additions and 26 deletions
|
@ -38,6 +38,7 @@ const (
|
|||
"ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `unique_mapping` UNIQUE (`user_id`, `folder_id`);" +
|
||||
"ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `folders_mapping_folder_id_fk_folders_id` FOREIGN KEY (`folder_id`) REFERENCES `{{folders}}` (`id`) ON DELETE CASCADE;" +
|
||||
"ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `folders_mapping_user_id_fk_users_id` FOREIGN KEY (`user_id`) REFERENCES `{{users}}` (`id`) ON DELETE CASCADE;"
|
||||
mysqlV6SQL = "ALTER TABLE `{{users}}` ADD COLUMN `additional_info` longtext NULL;"
|
||||
)
|
||||
|
||||
// MySQLProvider auth provider for MySQL/MariaDB database
|
||||
|
@ -217,6 +218,8 @@ func (p MySQLProvider) migrateDatabase() error {
|
|||
return updateMySQLDatabaseFromV3(p.dbHandle)
|
||||
case 4:
|
||||
return updateMySQLDatabaseFromV4(p.dbHandle)
|
||||
case 5:
|
||||
return updateMySQLDatabaseFromV5(p.dbHandle)
|
||||
default:
|
||||
return fmt.Errorf("Database version not handled: %v", dbVersion.Version)
|
||||
}
|
||||
|
@ -247,7 +250,15 @@ func updateMySQLDatabaseFromV3(dbHandle *sql.DB) error {
|
|||
}
|
||||
|
||||
func updateMySQLDatabaseFromV4(dbHandle *sql.DB) error {
|
||||
return updateMySQLDatabaseFrom4To5(dbHandle)
|
||||
err := updateMySQLDatabaseFrom4To5(dbHandle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return updateMySQLDatabaseFromV5(dbHandle)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFromV5(dbHandle *sql.DB) error {
|
||||
return updateMySQLDatabaseFrom5To6(dbHandle)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFrom1To2(dbHandle *sql.DB) error {
|
||||
|
@ -271,3 +282,10 @@ func updateMySQLDatabaseFrom3To4(dbHandle *sql.DB) error {
|
|||
func updateMySQLDatabaseFrom4To5(dbHandle *sql.DB) error {
|
||||
return sqlCommonUpdateDatabaseFrom4To5(dbHandle)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFrom5To6(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 5 -> 6")
|
||||
providerLog(logger.LevelInfo, "updating database version: 5 -> 6")
|
||||
sql := strings.Replace(mysqlV6SQL, "{{users}}", sqlTableUsers, 1)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 6)
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ ALTER TABLE "{{folders_mapping}}" ADD CONSTRAINT "folders_mapping_user_id_fk_use
|
|||
CREATE INDEX "folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id");
|
||||
CREATE INDEX "folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id");
|
||||
`
|
||||
pgsqlV6SQL = `ALTER TABLE "{{users}}" ADD COLUMN "additional_info" text NULL;`
|
||||
)
|
||||
|
||||
// PGSQLProvider auth provider for PostgreSQL database
|
||||
|
@ -216,6 +217,8 @@ func (p PGSQLProvider) migrateDatabase() error {
|
|||
return updatePGSQLDatabaseFromV3(p.dbHandle)
|
||||
case 4:
|
||||
return updatePGSQLDatabaseFromV4(p.dbHandle)
|
||||
case 5:
|
||||
return updatePGSQLDatabaseFromV5(p.dbHandle)
|
||||
default:
|
||||
return fmt.Errorf("Database version not handled: %v", dbVersion.Version)
|
||||
}
|
||||
|
@ -246,7 +249,15 @@ func updatePGSQLDatabaseFromV3(dbHandle *sql.DB) error {
|
|||
}
|
||||
|
||||
func updatePGSQLDatabaseFromV4(dbHandle *sql.DB) error {
|
||||
return updatePGSQLDatabaseFrom4To5(dbHandle)
|
||||
err := updatePGSQLDatabaseFrom4To5(dbHandle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return updatePGSQLDatabaseFromV5(dbHandle)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFromV5(dbHandle *sql.DB) error {
|
||||
return updatePGSQLDatabaseFrom5To6(dbHandle)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFrom1To2(dbHandle *sql.DB) error {
|
||||
|
@ -270,3 +281,10 @@ func updatePGSQLDatabaseFrom3To4(dbHandle *sql.DB) error {
|
|||
func updatePGSQLDatabaseFrom4To5(dbHandle *sql.DB) error {
|
||||
return sqlCommonUpdateDatabaseFrom4To5(dbHandle)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFrom5To6(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 5 -> 6")
|
||||
providerLog(logger.LevelInfo, "updating database version: 5 -> 6")
|
||||
sql := strings.Replace(pgsqlV6SQL, "{{users}}", sqlTableUsers, 1)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 6)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
sqlDatabaseVersion = 5
|
||||
sqlDatabaseVersion = 6
|
||||
initialDBVersionSQL = "INSERT INTO {{schema_version}} (version) VALUES (1);"
|
||||
defaultSQLQueryTimeout = 10 * time.Second
|
||||
longSQLQueryTimeout = 60 * time.Second
|
||||
|
@ -218,7 +218,7 @@ func sqlCommonAddUser(user User, dbHandle *sql.DB) error {
|
|||
}
|
||||
_, err = stmt.ExecContext(ctx, user.Username, user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
|
||||
user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth, user.Status, user.ExpirationDate, string(filters),
|
||||
string(fsConfig))
|
||||
string(fsConfig), user.AdditionalInfo)
|
||||
if err != nil {
|
||||
sqlCommonRollbackTransaction(tx)
|
||||
return err
|
||||
|
@ -272,7 +272,7 @@ func sqlCommonUpdateUser(user User, dbHandle *sql.DB) error {
|
|||
}
|
||||
_, err = stmt.ExecContext(ctx, user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
|
||||
user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth, user.Status, user.ExpirationDate,
|
||||
string(filters), string(fsConfig), user.ID)
|
||||
string(filters), string(fsConfig), user.AdditionalInfo, user.ID)
|
||||
if err != nil {
|
||||
sqlCommonRollbackTransaction(tx)
|
||||
return err
|
||||
|
@ -391,15 +391,18 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) {
|
|||
var publicKey sql.NullString
|
||||
var filters sql.NullString
|
||||
var fsConfig sql.NullString
|
||||
var additionalInfo sql.NullString
|
||||
var err error
|
||||
if row != nil {
|
||||
err = row.Scan(&user.ID, &user.Username, &password, &publicKey, &user.HomeDir, &user.UID, &user.GID, &user.MaxSessions,
|
||||
&user.QuotaSize, &user.QuotaFiles, &permissions, &user.UsedQuotaSize, &user.UsedQuotaFiles, &user.LastQuotaUpdate,
|
||||
&user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig)
|
||||
&user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig,
|
||||
&additionalInfo)
|
||||
} else {
|
||||
err = rows.Scan(&user.ID, &user.Username, &password, &publicKey, &user.HomeDir, &user.UID, &user.GID, &user.MaxSessions,
|
||||
&user.QuotaSize, &user.QuotaFiles, &permissions, &user.UsedQuotaSize, &user.UsedQuotaFiles, &user.LastQuotaUpdate,
|
||||
&user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig)
|
||||
&user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig,
|
||||
&additionalInfo)
|
||||
}
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -440,6 +443,9 @@ func getUserFromDbRow(row *sql.Row, rows *sql.Rows) (User, error) {
|
|||
user.FsConfig = fs
|
||||
}
|
||||
}
|
||||
if additionalInfo.Valid {
|
||||
user.AdditionalInfo = additionalInfo.String
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ ALTER TABLE "new__users" RENAME TO "{{users}}";
|
|||
CREATE INDEX "folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id");
|
||||
CREATE INDEX "folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id");
|
||||
`
|
||||
sqliteV6SQL = `ALTER TABLE "{{users}}" ADD COLUMN "additional_info" text NULL;`
|
||||
)
|
||||
|
||||
// SQLiteProvider auth provider for SQLite database
|
||||
|
@ -239,6 +240,8 @@ func (p SQLiteProvider) migrateDatabase() error {
|
|||
return updateSQLiteDatabaseFromV3(p.dbHandle)
|
||||
case 4:
|
||||
return updateSQLiteDatabaseFromV4(p.dbHandle)
|
||||
case 5:
|
||||
return updateSQLiteDatabaseFromV5(p.dbHandle)
|
||||
default:
|
||||
return fmt.Errorf("Database version not handled: %v", dbVersion.Version)
|
||||
}
|
||||
|
@ -269,7 +272,15 @@ func updateSQLiteDatabaseFromV3(dbHandle *sql.DB) error {
|
|||
}
|
||||
|
||||
func updateSQLiteDatabaseFromV4(dbHandle *sql.DB) error {
|
||||
return updateSQLiteDatabaseFrom4To5(dbHandle)
|
||||
err := updateSQLiteDatabaseFrom4To5(dbHandle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSQLiteDatabaseFromV5(dbHandle)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFromV5(dbHandle *sql.DB) error {
|
||||
return updateSQLiteDatabaseFrom5To6(dbHandle)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFrom1To2(dbHandle *sql.DB) error {
|
||||
|
@ -293,3 +304,10 @@ func updateSQLiteDatabaseFrom3To4(dbHandle *sql.DB) error {
|
|||
func updateSQLiteDatabaseFrom4To5(dbHandle *sql.DB) error {
|
||||
return sqlCommonUpdateDatabaseFrom4To5(dbHandle)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFrom5To6(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 5 -> 6")
|
||||
providerLog(logger.LevelInfo, "updating database version: 5 -> 6")
|
||||
sql := strings.Replace(sqliteV6SQL, "{{users}}", sqlTableUsers, 1)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 6)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
const (
|
||||
selectUserFields = "id,username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,used_quota_size," +
|
||||
"used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,expiration_date,last_login,status,filters,filesystem"
|
||||
"used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,expiration_date,last_login,status,filters,filesystem,additional_info"
|
||||
selectFolderFields = "id,path,used_quota_size,used_quota_files,last_quota_update"
|
||||
)
|
||||
|
||||
|
@ -72,19 +72,20 @@ func getQuotaQuery() string {
|
|||
func getAddUserQuery() string {
|
||||
return fmt.Sprintf(`INSERT INTO %v (username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,
|
||||
used_quota_size,used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,status,last_login,expiration_date,filters,
|
||||
filesystem)
|
||||
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v,%v,0,%v,%v,%v)`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1],
|
||||
filesystem,additional_info)
|
||||
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v,%v,0,%v,%v,%v,%v)`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1],
|
||||
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7],
|
||||
sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13],
|
||||
sqlPlaceholders[14], sqlPlaceholders[15])
|
||||
sqlPlaceholders[14], sqlPlaceholders[15], sqlPlaceholders[16])
|
||||
}
|
||||
|
||||
func getUpdateUserQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET password=%v,public_keys=%v,home_dir=%v,uid=%v,gid=%v,max_sessions=%v,quota_size=%v,
|
||||
quota_files=%v,permissions=%v,upload_bandwidth=%v,download_bandwidth=%v,status=%v,expiration_date=%v,filters=%v,filesystem=%v
|
||||
WHERE id = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3],
|
||||
quota_files=%v,permissions=%v,upload_bandwidth=%v,download_bandwidth=%v,status=%v,expiration_date=%v,filters=%v,filesystem=%v,
|
||||
additional_info=%v WHERE id = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3],
|
||||
sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9],
|
||||
sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], sqlPlaceholders[14], sqlPlaceholders[15])
|
||||
sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], sqlPlaceholders[14], sqlPlaceholders[15],
|
||||
sqlPlaceholders[16])
|
||||
}
|
||||
|
||||
func getDeleteUserQuery() string {
|
||||
|
|
|
@ -214,6 +214,8 @@ type User struct {
|
|||
Filters UserFilters `json:"filters"`
|
||||
// Filesystem configuration details
|
||||
FsConfig Filesystem `json:"filesystem"`
|
||||
// free form text field for external systems
|
||||
AdditionalInfo string `json:"additional_info,omitempty"`
|
||||
}
|
||||
|
||||
// GetFilesystem returns the filesystem for this user
|
||||
|
@ -849,6 +851,7 @@ func (u *User) getACopy() User {
|
|||
LastLogin: u.LastLogin,
|
||||
Filters: filters,
|
||||
FsConfig: fsConfig,
|
||||
AdditionalInfo: u.AdditionalInfo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ For each account, the following properties can be configured:
|
|||
- `az_upload_concurrency`, how many parts are uploaded in parallel. Zero means the default (2)
|
||||
- `az_key_prefix`, allows to restrict access to the folder identified by this prefix and its contents
|
||||
- `az_use_emulator`, boolean
|
||||
- `additional_info`, string. Free text field
|
||||
|
||||
These properties are stored inside the data provider.
|
||||
|
||||
|
|
|
@ -44,13 +44,14 @@ Let's see a sample usage for each REST API.
|
|||
Command:
|
||||
|
||||
```console
|
||||
python sftpgo_api_cli add-user test_username --password "test_pwd" --home-dir="/tmp/test_home_dir" --uid 33 --gid 1000 --max-sessions 2 --quota-size 0 --quota-files 3 --permissions "list" "download" "upload" "delete" "rename" "create_dirs" "overwrite" --subdirs-permissions "/dir1::list,download" "/dir2::*" --upload-bandwidth 100 --download-bandwidth 60 --status 0 --expiration-date 2019-01-01 --allowed-ip "192.168.1.1/32" --fs S3 --s3-bucket test --s3-region eu-west-1 --s3-access-key accesskey --s3-access-secret secret --s3-endpoint "http://127.0.0.1:9000" --s3-storage-class Standard --s3-key-prefix "vfolder/" --s3-upload-part-size 10 --s3-upload-concurrency 4 --denied-login-methods "password" "keyboard-interactive" --allowed-patterns "/dir1::*.jpg,*.png" "/dir2::*.rar,*.png" --denied-patterns "/dir3::*.zip,*.rar" --denied-protocols DAV FTP
|
||||
python sftpgo_api_cli add-user test_username --password "test_pwd" --home-dir="/tmp/test_home_dir" --uid 33 --gid 1000 --max-sessions 2 --quota-size 0 --quota-files 3 --permissions "list" "download" "upload" "delete" "rename" "create_dirs" "overwrite" --subdirs-permissions "/dir1::list,download" "/dir2::*" --upload-bandwidth 100 --download-bandwidth 60 --status 0 --expiration-date 2019-01-01 --allowed-ip "192.168.1.1/32" --fs S3 --s3-bucket test --s3-region eu-west-1 --s3-access-key accesskey --s3-access-secret secret --s3-endpoint "http://127.0.0.1:9000" --s3-storage-class Standard --s3-key-prefix "vfolder/" --s3-upload-part-size 10 --s3-upload-concurrency 4 --denied-login-methods "password" "keyboard-interactive" --allowed-patterns "/dir1::*.jpg,*.png" "/dir2::*.rar,*.png" --denied-patterns "/dir3::*.zip,*.rar" --denied-protocols DAV FTP --additional-info "sample info"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
{
|
||||
"additional_info": "sample info",
|
||||
"download_bandwidth": 60,
|
||||
"expiration_date": 1546297200000,
|
||||
"filesystem": {
|
||||
|
|
|
@ -84,11 +84,11 @@ class SFTPGoApiRequests:
|
|||
denied_patterns=[], allowed_patterns=[], s3_upload_part_size=0, s3_upload_concurrency=0,
|
||||
max_upload_file_size=0, denied_protocols=[], az_container='', az_account_name='', az_account_key='',
|
||||
az_sas_url='', az_endpoint='', az_upload_part_size=0, az_upload_concurrency=0, az_key_prefix='',
|
||||
az_use_emulator=False, az_access_tier=''):
|
||||
az_use_emulator=False, az_access_tier='', additional_info=''):
|
||||
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}
|
||||
'status':status, 'expiration_date':expiration_date, 'additional_info':additional_info}
|
||||
if password is not None:
|
||||
user.update({'password':password})
|
||||
if public_keys:
|
||||
|
@ -285,7 +285,7 @@ class SFTPGoApiRequests:
|
|||
denied_login_methods=[], virtual_folders=[], denied_patterns=[], allowed_patterns=[],
|
||||
s3_upload_part_size=0, s3_upload_concurrency=0, max_upload_file_size=0, denied_protocols=[], az_container="",
|
||||
az_account_name='', az_account_key='', az_sas_url='', az_endpoint='', az_upload_part_size=0,
|
||||
az_upload_concurrency=0, az_key_prefix='', az_use_emulator=False, az_access_tier=''):
|
||||
az_upload_concurrency=0, az_key_prefix='', az_use_emulator=False, az_access_tier='', additional_info=''):
|
||||
u = self.buildUserObject(0, username, password, public_keys, home_dir, uid, gid, max_sessions,
|
||||
quota_size, quota_files, self.buildPermissions(perms, subdirs_permissions), upload_bandwidth, download_bandwidth,
|
||||
status, expiration_date, allowed_ip, denied_ip, fs_provider, s3_bucket, s3_region, s3_access_key,
|
||||
|
@ -293,7 +293,7 @@ class SFTPGoApiRequests:
|
|||
gcs_credentials_file, gcs_automatic_credentials, denied_login_methods, virtual_folders, denied_patterns,
|
||||
allowed_patterns, s3_upload_part_size, s3_upload_concurrency, max_upload_file_size, denied_protocols,
|
||||
az_container, az_account_name, az_account_key, az_sas_url, az_endpoint, az_upload_part_size,
|
||||
az_upload_concurrency, az_key_prefix, az_use_emulator, az_access_tier)
|
||||
az_upload_concurrency, az_key_prefix, az_use_emulator, az_access_tier, additional_info)
|
||||
r = requests.post(self.userPath, json=u, auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
||||
|
@ -306,7 +306,7 @@ class SFTPGoApiRequests:
|
|||
allowed_patterns=[], s3_upload_part_size=0, s3_upload_concurrency=0, max_upload_file_size=0,
|
||||
denied_protocols=[], disconnect=0, az_container='', az_account_name='', az_account_key='', az_sas_url='',
|
||||
az_endpoint='', az_upload_part_size=0, az_upload_concurrency=0, az_key_prefix='', az_use_emulator=False,
|
||||
az_access_tier=''):
|
||||
az_access_tier='', additional_info=''):
|
||||
u = self.buildUserObject(user_id, username, password, public_keys, home_dir, uid, gid, max_sessions,
|
||||
quota_size, quota_files, self.buildPermissions(perms, subdirs_permissions), upload_bandwidth, download_bandwidth,
|
||||
status, expiration_date, allowed_ip, denied_ip, fs_provider, s3_bucket, s3_region, s3_access_key,
|
||||
|
@ -314,7 +314,7 @@ class SFTPGoApiRequests:
|
|||
gcs_credentials_file, gcs_automatic_credentials, denied_login_methods, virtual_folders, denied_patterns,
|
||||
allowed_patterns, s3_upload_part_size, s3_upload_concurrency, max_upload_file_size, denied_protocols,
|
||||
az_container, az_account_name, az_account_key, az_sas_url, az_endpoint, az_upload_part_size,
|
||||
az_upload_concurrency, az_key_prefix, az_use_emulator, az_access_tier)
|
||||
az_upload_concurrency, az_key_prefix, az_use_emulator, az_access_tier, additional_info)
|
||||
r = requests.put(urlparse.urljoin(self.userPath, 'user/' + str(user_id)), params={'disconnect':disconnect},
|
||||
json=u, auth=self.auth, verify=self.verify)
|
||||
self.printResponse(r)
|
||||
|
@ -608,6 +608,7 @@ def addCommonUserArguments(parser):
|
|||
help='User\'s status. 1 enabled, 0 disabled. Default: %(default)s')
|
||||
parser.add_argument('--max-upload-file-size', type=int, default=0,
|
||||
help='Maximum allowed size, as bytes, for a single file upload, 0 means unlimited. Default: %(default)s')
|
||||
parser.add_argument('--additional-info', type=str, default='', help='Free form text field. Default: %(default)s')
|
||||
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=[],
|
||||
|
@ -815,7 +816,7 @@ if __name__ == '__main__':
|
|||
args.s3_upload_part_size, args.s3_upload_concurrency, args.max_upload_file_size, args.denied_protocols,
|
||||
args.az_container, args.az_account_name, args.az_account_key, args.az_sas_url, args.az_endpoint,
|
||||
args.az_upload_part_size, args.az_upload_concurrency, args.az_key_prefix, args.az_use_emulator,
|
||||
args.az_access_tier)
|
||||
args.az_access_tier, args.additional_info)
|
||||
elif args.command == 'update-user':
|
||||
api.updateUser(args.id, args.username, args.password, args.public_keys, args.home_dir, args.uid, args.gid,
|
||||
args.max_sessions, args.quota_size, args.quota_files, args.permissions, args.upload_bandwidth,
|
||||
|
@ -828,7 +829,7 @@ if __name__ == '__main__':
|
|||
args.s3_upload_concurrency, args.max_upload_file_size, args.denied_protocols, args.disconnect,
|
||||
args.az_container, args.az_account_name, args.az_account_key, args.az_sas_url, args.az_endpoint,
|
||||
args.az_upload_part_size, args.az_upload_concurrency, args.az_key_prefix, args.az_use_emulator,
|
||||
args.az_access_tier)
|
||||
args.az_access_tier, args.additional_info)
|
||||
elif args.command == 'delete-user':
|
||||
api.deleteUser(args.id)
|
||||
elif args.command == 'get-users':
|
||||
|
|
|
@ -704,6 +704,9 @@ func compareAzBlobConfig(expected *dataprovider.User, actual *dataprovider.User)
|
|||
if expected.FsConfig.AzBlobConfig.UseEmulator != actual.FsConfig.AzBlobConfig.UseEmulator {
|
||||
return errors.New("Azure Blob use emulator mismatch")
|
||||
}
|
||||
if expected.FsConfig.AzBlobConfig.AccessTier != actual.FsConfig.AzBlobConfig.AccessTier {
|
||||
return errors.New("Azure Blob access tier mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -861,6 +864,9 @@ func compareEqualsUserFields(expected *dataprovider.User, actual *dataprovider.U
|
|||
if expected.ExpirationDate != actual.ExpirationDate {
|
||||
return errors.New("ExpirationDate mismatch")
|
||||
}
|
||||
if expected.AdditionalInfo != actual.AdditionalInfo {
|
||||
return errors.New("AdditionalInfo mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ func TestBasicUserHandling(t *testing.T) {
|
|||
user.UploadBandwidth = 128
|
||||
user.DownloadBandwidth = 64
|
||||
user.ExpirationDate = utils.GetTimeAsMsSinceEpoch(time.Now())
|
||||
|
||||
user.AdditionalInfo = "some free text"
|
||||
originalUser := user
|
||||
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
|
@ -2715,6 +2715,7 @@ func TestWebUserAddMock(t *testing.T) {
|
|||
user.UploadBandwidth = 32
|
||||
user.DownloadBandwidth = 64
|
||||
user.UID = 1000
|
||||
user.AdditionalInfo = "info"
|
||||
mappedDir := filepath.Join(os.TempDir(), "mapped")
|
||||
form := make(url.Values)
|
||||
form.Set("username", user.Username)
|
||||
|
@ -2729,6 +2730,7 @@ func TestWebUserAddMock(t *testing.T) {
|
|||
form.Set("denied_extensions", "/dir2::.webp,.webp\n/dir2::.tiff\n/dir1::.zip")
|
||||
form.Set("allowed_patterns", "/dir2::*.jpg,*.png\n/dir1::*.png")
|
||||
form.Set("denied_patterns", "/dir1::*.zip\n/dir3::*.rar\n/dir2::*.mkv")
|
||||
form.Set("additional_info", user.AdditionalInfo)
|
||||
b, contentType, _ := getMultipartFormData(form, "", "")
|
||||
// test invalid url escape
|
||||
req, _ := http.NewRequest(http.MethodPost, webUserPath+"?a=%2", &b)
|
||||
|
@ -2848,6 +2850,7 @@ func TestWebUserAddMock(t *testing.T) {
|
|||
assert.Equal(t, user.UploadBandwidth, newUser.UploadBandwidth)
|
||||
assert.Equal(t, user.DownloadBandwidth, newUser.DownloadBandwidth)
|
||||
assert.Equal(t, int64(1000), newUser.Filters.MaxUploadFileSize)
|
||||
assert.Equal(t, user.AdditionalInfo, newUser.AdditionalInfo)
|
||||
assert.True(t, utils.IsStringInSlice(testPubKey, newUser.PublicKeys))
|
||||
if val, ok := newUser.Permissions["/subdir"]; ok {
|
||||
assert.True(t, utils.IsStringInSlice(dataprovider.PermListItems, val))
|
||||
|
@ -2926,6 +2929,7 @@ func TestWebUserUpdateMock(t *testing.T) {
|
|||
user.QuotaFiles = 2
|
||||
user.QuotaSize = 3
|
||||
user.GID = 1000
|
||||
user.AdditionalInfo = "new additional info"
|
||||
form := make(url.Values)
|
||||
form.Set("username", user.Username)
|
||||
form.Set("home_dir", user.HomeDir)
|
||||
|
@ -2947,6 +2951,7 @@ func TestWebUserUpdateMock(t *testing.T) {
|
|||
form.Set("denied_protocols", common.ProtocolFTP)
|
||||
form.Set("max_upload_file_size", "100")
|
||||
form.Set("disconnect", "1")
|
||||
form.Set("additional_info", user.AdditionalInfo)
|
||||
b, contentType, _ := getMultipartFormData(form, "", "")
|
||||
req, _ = http.NewRequest(http.MethodPost, webUserPath+"/"+strconv.FormatInt(user.ID, 10), &b)
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
@ -2966,6 +2971,7 @@ func TestWebUserUpdateMock(t *testing.T) {
|
|||
assert.Equal(t, user.QuotaSize, updateUser.QuotaSize)
|
||||
assert.Equal(t, user.UID, updateUser.UID)
|
||||
assert.Equal(t, user.GID, updateUser.GID)
|
||||
assert.Equal(t, user.AdditionalInfo, updateUser.AdditionalInfo)
|
||||
assert.Equal(t, int64(100), updateUser.Filters.MaxUploadFileSize)
|
||||
|
||||
if val, ok := updateUser.Permissions["/otherdir"]; ok {
|
||||
|
|
|
@ -311,6 +311,10 @@ func TestCompareUserFields(t *testing.T) {
|
|||
expected.ExpirationDate = 123
|
||||
err = compareEqualsUserFields(expected, actual)
|
||||
assert.Error(t, err)
|
||||
expected.ExpirationDate = 0
|
||||
expected.AdditionalInfo = "info"
|
||||
err = compareEqualsUserFields(expected, actual)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCompareUserFsConfig(t *testing.T) {
|
||||
|
@ -443,6 +447,10 @@ func TestCompareUserAzureConfig(t *testing.T) {
|
|||
err = compareUserFsConfig(expected, actual)
|
||||
assert.Error(t, err)
|
||||
expected.FsConfig.AzBlobConfig.UseEmulator = false
|
||||
expected.FsConfig.AzBlobConfig.AccessTier = "Hot"
|
||||
err = compareUserFsConfig(expected, actual)
|
||||
assert.Error(t, err)
|
||||
expected.FsConfig.AzBlobConfig.AccessTier = ""
|
||||
}
|
||||
|
||||
func TestGCSWebInvalidFormFile(t *testing.T) {
|
||||
|
|
|
@ -2,7 +2,7 @@ openapi: 3.0.3
|
|||
info:
|
||||
title: SFTPGo
|
||||
description: 'SFTPGo REST API'
|
||||
version: 2.1.0
|
||||
version: 2.1.1
|
||||
|
||||
servers:
|
||||
- url: /api/v1
|
||||
|
@ -1239,6 +1239,9 @@ components:
|
|||
$ref: '#/components/schemas/UserFilters'
|
||||
filesystem:
|
||||
$ref: '#/components/schemas/FilesystemConfig'
|
||||
additional_info:
|
||||
type: string
|
||||
description: Free form text field for external systems
|
||||
Transfer:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -589,6 +589,7 @@ func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
|
|||
ExpirationDate: expirationDateMillis,
|
||||
Filters: getFiltersFromUserPostFields(r),
|
||||
FsConfig: fsConfig,
|
||||
AdditionalInfo: r.Form.Get("additional_info"),
|
||||
}
|
||||
maxFileSize, err := strconv.ParseInt(r.Form.Get("max_upload_file_size"), 10, 64)
|
||||
user.Filters.MaxUploadFileSize = maxFileSize
|
||||
|
|
|
@ -517,6 +517,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idAdditionalInfo" class="col-sm-2 col-form-label">Additional info</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" id="idAdditionalInfo" name="additional_info" rows="3"
|
||||
aria-describedby="additionalInfoHelpBlock">{{.User.AdditionalInfo}}</textarea>
|
||||
<small id="additionalInfoHelpBlock" class="form-text text-muted">
|
||||
Free form text field
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if not .IsAdd}}
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
|
|
Loading…
Reference in a new issue