From 8b236e7bc8238a2b90ae1524eef2ce120f53ad9d Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Fri, 13 Jan 2023 12:29:21 +0530 Subject: [PATCH] Ensure that the authenticated users can't access each other's directories and files by providing relative paths. #5734 --- web/pgadmin/tools/backup/__init__.py | 41 ++-------- web/pgadmin/tools/import_export/__init__.py | 36 ++------- .../tools/import_export_servers/__init__.py | 13 +++- web/pgadmin/tools/restore/__init__.py | 30 ++------ web/pgadmin/utils/__init__.py | 75 ++++++++++++------- web/yarn.lock | 8 +- 6 files changed, 77 insertions(+), 126 deletions(-) diff --git a/web/pgadmin/tools/backup/__init__.py b/web/pgadmin/tools/backup/__init__.py index 0a0567d07..6b27b38ae 100644 --- a/web/pgadmin/tools/backup/__init__.py +++ b/web/pgadmin/tools/backup/__init__.py @@ -19,8 +19,9 @@ from flask_babel import gettext as _ from flask_security import login_required, current_user from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc from pgadmin.utils import PgAdminModule, get_storage_directory, html, \ - fs_short_path, document_dir, does_utility_exist, get_server -from pgadmin.utils.ajax import make_json_response, bad_request + fs_short_path, document_dir, does_utility_exist, get_server, \ + filename_with_file_manager_path +from pgadmin.utils.ajax import make_json_response, bad_request, unauthorized from config import PG_DEFAULT_DRIVER from pgadmin.model import Server, SharedServer @@ -189,40 +190,6 @@ def script(): ) -def filename_with_file_manager_path(_file, create_file=True): - """ - Args: - file: File name returned from client file manager - create_file: Set flag to False when file creation doesn't required - Returns: - Filename to use for backup with full path taken from preference - """ - # Set file manager directory from preference - storage_dir = get_storage_directory() - if storage_dir: - _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\')) - elif not os.path.isabs(_file): - _file = os.path.join(document_dir(), _file) - - def short_filepath(): - short_path = fs_short_path(_file) - # fs_short_path() function may return empty path on Windows - # if directory doesn't exists. In that case we strip the last path - # component and get the short path. - if os.name == 'nt' and short_path == '': - base_name = os.path.basename(_file) - dir_name = os.path.dirname(_file) - short_path = fs_short_path(dir_name) + '\\' + base_name - return short_path - - if create_file: - # Touch the file to get the short path of the file on windows. - with open(_file, 'a'): - return short_filepath() - - return short_filepath() - - def _get_args_params_values(data, conn, backup_obj_type, backup_file, server, manager): """ @@ -367,6 +334,8 @@ def create_backup_objects_job(sid): try: backup_file = filename_with_file_manager_path( data['file'], (data.get('format', '') != 'directory')) + except PermissionError as e: + return unauthorized(errormsg=str(e)) except Exception as e: return bad_request(errormsg=str(e)) diff --git a/web/pgadmin/tools/import_export/__init__.py b/web/pgadmin/tools/import_export/__init__.py index db1e0d262..6308f9dbf 100644 --- a/web/pgadmin/tools/import_export/__init__.py +++ b/web/pgadmin/tools/import_export/__init__.py @@ -17,8 +17,9 @@ from flask_babel import gettext as _ from flask_security import login_required, current_user from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc from pgadmin.utils import PgAdminModule, get_storage_directory, html, \ - fs_short_path, document_dir, IS_WIN, does_utility_exist -from pgadmin.utils.ajax import make_json_response, bad_request + fs_short_path, document_dir, IS_WIN, does_utility_exist, \ + filename_with_file_manager_path +from pgadmin.utils.ajax import make_json_response, bad_request, unauthorized from config import PG_DEFAULT_DRIVER from pgadmin.model import Server @@ -145,33 +146,6 @@ def script(): ) -def filename_with_file_manager_path(_file, _present=False): - """ - Args: - file: File name returned from client file manager - - Returns: - Filename to use for backup with full path taken from preference - """ - # Set file manager directory from preference - storage_dir = get_storage_directory() - - if storage_dir: - _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\')) - elif not os.path.isabs(_file): - _file = os.path.join(document_dir(), _file) - - if not _present: - # Touch the file to get the short path of the file on windows. - with open(_file, 'a'): - return fs_short_path(_file) - else: - if not os.path.isfile(_file): - return None - - return fs_short_path(_file) - - def _get_ignored_column_list(data, driver, conn): """ Get list of ignored columns for import/export. @@ -297,7 +271,9 @@ def create_import_export_job(sid): if 'filename' in data: try: _file = filename_with_file_manager_path( - data['filename'], data['is_import']) + data['filename'], not data['is_import']) + except PermissionError as e: + return unauthorized(errormsg=str(e)) except Exception as e: return bad_request(errormsg=str(e)) diff --git a/web/pgadmin/tools/import_export_servers/__init__.py b/web/pgadmin/tools/import_export_servers/__init__.py index 755507774..91ec2ac62 100644 --- a/web/pgadmin/tools/import_export_servers/__init__.py +++ b/web/pgadmin/tools/import_export_servers/__init__.py @@ -20,10 +20,11 @@ from flask_security import login_required, current_user from pgadmin.utils import PgAdminModule from pgadmin.utils.ajax import bad_request from pgadmin.utils.constants import MIMETYPE_APP_JS -from pgadmin.utils.ajax import make_json_response, internal_server_error +from pgadmin.utils.ajax import make_json_response, internal_server_error, \ + unauthorized from pgadmin.model import ServerGroup, Server from pgadmin.utils import clear_database_servers, dump_database_servers,\ - load_database_servers, validate_json_data + load_database_servers, validate_json_data, filename_with_file_manager_path from urllib.parse import unquote from pgadmin.utils.paths import get_storage_directory @@ -118,6 +119,14 @@ def load_servers(): # retrieve storage directory path storage_manager_path = get_storage_directory() + + try: + file_path = filename_with_file_manager_path(file_path) + except PermissionError as e: + return unauthorized(errormsg=str(e)) + except Exception as e: + return bad_request(errormsg=str(e)) + if storage_manager_path: # generate full path of file file_path = os.path.join( diff --git a/web/pgadmin/tools/restore/__init__.py b/web/pgadmin/tools/restore/__init__.py index 0a242bf84..fc2cb998f 100644 --- a/web/pgadmin/tools/restore/__init__.py +++ b/web/pgadmin/tools/restore/__init__.py @@ -18,8 +18,10 @@ from flask_babel import gettext as _ from flask_security import login_required, current_user from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc from pgadmin.utils import PgAdminModule, get_storage_directory, html, \ - fs_short_path, document_dir, does_utility_exist, get_server -from pgadmin.utils.ajax import make_json_response, bad_request + fs_short_path, document_dir, does_utility_exist, get_server, \ + filename_with_file_manager_path +from pgadmin.utils.ajax import make_json_response, bad_request, \ + internal_server_error from config import PG_DEFAULT_DRIVER from pgadmin.model import Server, SharedServer @@ -129,28 +131,6 @@ def script(): ) -def filename_with_file_manager_path(_file): - """ - Args: - file: File name returned from client file manager - - Returns: - Filename to use for backup with full path taken from preference - """ - # Set file manager directory from preference - storage_dir = get_storage_directory() - - if storage_dir: - _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\')) - elif not os.path.isabs(_file): - _file = os.path.join(document_dir(), _file) - - if not os.path.isfile(_file) and not os.path.exists(_file): - return None - - return fs_short_path(_file) - - def _get_create_req_data(): """ Get data from request for create restore job. @@ -164,7 +144,7 @@ def _get_create_req_data(): try: _file = filename_with_file_manager_path(data['file']) except Exception as e: - return True, bad_request(errormsg=str(e)), data + return True, internal_server_error(errormsg=str(e)), data, None if _file is None: return True, make_json_response( diff --git a/web/pgadmin/utils/__init__.py b/web/pgadmin/utils/__init__.py index dbca36165..cd86c022f 100644 --- a/web/pgadmin/utils/__init__.py +++ b/web/pgadmin/utils/__init__.py @@ -260,6 +260,45 @@ def get_complete_file_path(file, validate=True): return file +def filename_with_file_manager_path(_file, create_file=False, + skip_permission_check=False): + """ + Args: + file: File name returned from client file manager + create_file: Set flag to False when file creation doesn't required + Returns: + Filename to use for backup with full path taken from preference + """ + # Set file manager directory from preference + storage_dir = get_storage_directory() + + from pgadmin.misc.file_manager import Filemanager + Filemanager.check_access_permission( + storage_dir, _file, skip_permission_check) + if storage_dir: + _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\')) + elif not os.path.isabs(_file): + _file = os.path.join(document_dir(), _file) + + def short_filepath(): + short_path = fs_short_path(_file) + # fs_short_path() function may return empty path on Windows + # if directory doesn't exists. In that case we strip the last path + # component and get the short path. + if os.name == 'nt' and short_path == '': + base_name = os.path.basename(_file) + dir_name = os.path.dirname(_file) + short_path = fs_short_path(dir_name) + '\\' + base_name + return short_path + + if create_file: + # Touch the file to get the short path of the file on windows. + with open(_file, 'a'): + return short_filepath() + + return short_filepath() + + def does_utility_exist(file): """ This function will check the utility file exists on given path. @@ -434,27 +473,12 @@ def dump_database_servers(output_file, selected_servers, object_dict["Servers"] = server_dict - # retrieve storage directory path - storage_manager_path = None - if not from_setup: - storage_manager_path = get_storage_directory(user) - - # generate full path of file - file_path = unquote(output_file) - - from pgadmin.misc.file_manager import Filemanager try: - Filemanager.check_access_permission(storage_manager_path, file_path, - from_setup) + file_path = filename_with_file_manager_path( + unquote(output_file), skip_permission_check=from_setup) except Exception as e: return _handle_error(str(e), from_setup) - if storage_manager_path is not None: - file_path = os.path.join( - storage_manager_path, - file_path.lstrip('/').lstrip('\\') - ) - # write to file file_content = json.dumps(object_dict, indent=4) error_str = "Error: {0}" @@ -548,19 +572,12 @@ def load_database_servers(input_file, selected_servers, if user is None: return False, USER_NOT_FOUND % load_user - # retrieve storage directory path - storage_manager_path = None - if not from_setup: - storage_manager_path = get_storage_directory(user) - # generate full path of file - file_path = unquote(input_file) - if storage_manager_path: - # generate full path of file - file_path = os.path.join( - storage_manager_path, - file_path.lstrip('/').lstrip('\\') - ) + try: + file_path = filename_with_file_manager_path( + unquote(input_file), skip_permission_check=from_setup) + except Exception as e: + return _handle_error(str(e), from_setup) try: with open(file_path) as f: diff --git a/web/yarn.lock b/web/yarn.lock index 96d52e5a2..df20a984a 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -8517,9 +8517,9 @@ react-checkbox-tree@^1.7.2: nanoid "^3.0.0" prop-types "^15.5.8" -"react-data-grid@git+https://github.com/EnterpriseDB/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8": +"react-data-grid@git+https://github.com/pgadmin-org/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8": version "7.0.0-beta.14" - resolved "git+https://github.com/EnterpriseDB/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8" + resolved "git+https://github.com/pgadmin-org/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8" dependencies: clsx "^1.1.1" @@ -10337,9 +10337,9 @@ watchpack@^2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -"webcabin-docker@git+https://github.com/EnterpriseDB/wcDocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8": +"webcabin-docker@git+https://github.com/pgadmin-org/wcdocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8": version "2.2.5" - resolved "git+https://github.com/EnterpriseDB/wcDocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8" + resolved "git+https://github.com/pgadmin-org/wcdocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8" dependencies: "@fortawesome/fontawesome-free" "^5.14.0" FileSaver "^0.10.0"