From e4eeba2aa7c0b614b64d3e476a6db9450203864f Mon Sep 17 00:00:00 2001 From: Nikhil Mohite Date: Fri, 24 Mar 2023 11:08:27 +0530 Subject: [PATCH] Fixed the UI related issues reported during testing for Shared Storage in Server Mode. #5014 --- docs/en_US/storage_manager.rst | 12 +++++++----- web/pgadmin/browser/__init__.py | 10 ++++++++-- .../browser/templates/browser/js/utils.js | 1 + web/pgadmin/misc/file_manager/__init__.py | 10 +++++----- .../file_manager/static/js/FileManagerModule.jsx | 1 + .../static/js/components/FileManager.jsx | 15 ++++++++------- web/pgadmin/tools/sqleditor/__init__.py | 16 ++++++++++++++-- .../static/js/components/QueryToolComponent.jsx | 4 +++- web/pgadmin/utils/constants.py | 3 +++ .../javascript/file_manager/FileManager.spec.js | 4 +++- 10 files changed, 53 insertions(+), 23 deletions(-) diff --git a/docs/en_US/storage_manager.rst b/docs/en_US/storage_manager.rst index af6a441e2..4fecf1e93 100644 --- a/docs/en_US/storage_manager.rst +++ b/docs/en_US/storage_manager.rst @@ -53,12 +53,14 @@ Shared Storage :alt: Other options :align: center -In shared storage the ``My Storage`` is the user's storage directory, and other directories are shared storage set by -the admin user. Using this shared storage users can share the files with other users through pgAdmin. -Admin users can mark the shared storage as restricted to restrict non-admin users from deleting, uploading, -adding, and renaming files/folders in shared storage by setting the restricted_access flag in config. -*NOTE: You must ensure the directories specified are writeable by the user that the web server processes will be running as, e.g. apache or www-data.* +In the storage manager, ``My Storage`` is the pgAdmin user’s storage directory, and other listed directories are shared +storages set by the pgAdmin server administrator. Using these, pgAdmin users can have common storages to share files. +pgAdmin server administrator can configure the shared storages using the config file. Storages can be +marked as restricted to give read-only access to non-admin pgAdmin users. + + +.. note:: You must ensure the directories specified are writeable by the user that the web server processes will be running as, e.g. apache or www-data.* Other Options diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index e324d5a96..2a5a5ed88 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -559,8 +559,11 @@ def get_shared_storage_list(): config.SHARED_STORAGE = shared_storage_config shared_storage_list = [sdir['name'] for sdir in shared_storage_config] + restricted_shared_storage_list = [sdir['name'] for sdir in + shared_storage_config if + sdir['restricted_access']] - return shared_storage_list + return shared_storage_list, restricted_shared_storage_list @blueprint.route("/js/utils.js") @@ -630,7 +633,8 @@ def utils(): auth_source = session['auth_source_manager'][ 'source_friendly_name'] - shared_storage_list = get_shared_storage_list() + shared_storage_list, \ + restricted_shared_storage_list = get_shared_storage_list() return make_response( render_template( @@ -665,6 +669,8 @@ def utils(): password_length_min=config.PASSWORD_LENGTH_MIN, current_ui_lock=current_ui_lock, shared_storage_list=shared_storage_list, + restricted_shared_storage_list=[] if current_user.has_role( + "Administrator") else restricted_shared_storage_list, ), 200, {'Content-Type': MIMETYPE_APP_JS}) diff --git a/web/pgadmin/browser/templates/browser/js/utils.js b/web/pgadmin/browser/templates/browser/js/utils.js index e44dcfc77..676292b24 100644 --- a/web/pgadmin/browser/templates/browser/js/utils.js +++ b/web/pgadmin/browser/templates/browser/js/utils.js @@ -57,6 +57,7 @@ define('pgadmin.browser.utils', /* GET PSQL Tool related config */ pgAdmin['enable_psql'] = '{{enable_psql}}' == 'True'; pgAdmin['shared_storage'] = {{shared_storage_list}} + pgAdmin['restricted_shared_storage'] = {{restricted_shared_storage_list}} pgAdmin['platform'] = '{{platform}}'; pgAdmin['qt_default_placeholder'] = '{{qt_default_placeholder}}' pgAdmin['vw_edt_default_placeholder'] = '{{vw_edt_default_placeholder}}' diff --git a/web/pgadmin/misc/file_manager/__init__.py b/web/pgadmin/misc/file_manager/__init__.py index 15e5917bb..3b34bf968 100644 --- a/web/pgadmin/misc/file_manager/__init__.py +++ b/web/pgadmin/misc/file_manager/__init__.py @@ -17,6 +17,7 @@ import time from urllib.parse import unquote from sys import platform as _platform from flask_security import current_user +from pgadmin.utils.constants import ACCESS_DENIED_MESSAGE import config import codecs import pathlib @@ -792,11 +793,10 @@ class Filemanager(): if selectedDir[ 'restricted_access'] and not current_user.has_role( "Administrator"): - raise PermissionError(gettext( - "Access denied: This shared folder has restricted " - "access, you are not allowed to rename, delete, " - "or upload any files/folders. Please contact " - "Administrator to gain access.")) + return make_json_response(success=0, + errormsg=ACCESS_DENIED_MESSAGE, + info='ACCESS_DENIED', + status=403) def rename(self, old=None, new=None): """ diff --git a/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx b/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx index d7f4672a2..cd77dead3 100644 --- a/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx +++ b/web/pgadmin/misc/file_manager/static/js/FileManagerModule.jsx @@ -75,6 +75,7 @@ export default class FileManagerModule { onCancel={onCancel} onOK={onOK} sharedStorages={this.pgAdmin.server_mode == 'True' ? this.pgAdmin.shared_storage: []} + restrictedSharedStorage={this.pgAdmin.server_mode == 'True' ? this.pgAdmin.restricted_shared_storage: []} /> ); }, { diff --git a/web/pgadmin/misc/file_manager/static/js/components/FileManager.jsx b/web/pgadmin/misc/file_manager/static/js/components/FileManager.jsx index 8036976a5..220fb1513 100644 --- a/web/pgadmin/misc/file_manager/static/js/components/FileManager.jsx +++ b/web/pgadmin/misc/file_manager/static/js/components/FileManager.jsx @@ -402,7 +402,7 @@ ConfirmFile.propTypes = { onNo: PropTypes.func }; -export default function FileManager({params, closeModal, onOK, onCancel, sharedStorages=[]}) { +export default function FileManager({params, closeModal, onOK, onCancel, sharedStorages=[], restrictedSharedStorage=[]}) { const classes = useStyles(); const modalClasses = useModalStyles(); const apiObj = useMemo(()=>getApiInstance(), []); @@ -462,7 +462,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS let newItems = await fmUtilsObj.getFolder(dirPath || fmUtilsObj.currPath, changeStoragePath); setItems(newItems); setPath(fmUtilsObj.currPath); - params.dialog_type == 'storage_dialog' && fmUtilsObj.setLastVisitedDir(fmUtilsObj.currPath, selectedSS); + setTimeout(()=>{fmUtilsObj.setLastVisitedDir(dirPath || fmUtilsObj.currPath, changeStoragePath);}, 100); } catch (error) { console.error(error); setErrorMsg(parseApiError(error)); @@ -726,7 +726,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS {params.dialog_type == 'storage_dialog' && } onClick={onDownload} disabled={showUploader || isNoneSelected || selectedRow?.file_type == 'dir' || selectedRow?.file_type == 'drive'} />} - {fmUtilsObj.hasCapability('create') && } + {fmUtilsObj.hasCapability('create') && !restrictedSharedStorage.includes(selectedSS) && } onClick={onAddFolder} disabled={showUploader} />} @@ -739,13 +739,13 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS onClose={onMenuClose} label={gettext('Options')} > - {fmUtilsObj.hasCapability('rename') && + {fmUtilsObj.hasCapability('rename') && !restrictedSharedStorage.includes(selectedSS) && {gettext('Rename')} } - {fmUtilsObj.hasCapability('delete') && + {fmUtilsObj.hasCapability('delete') && !restrictedSharedStorage.includes(selectedSS) && {gettext('Delete')} } - {fmUtilsObj.hasCapability('upload') && <> + {fmUtilsObj.hasCapability('upload') && !restrictedSharedStorage.includes(selectedSS) && <> { e.keepOpen = false; @@ -820,7 +820,7 @@ export default function FileManager({params, closeModal, onOK, onCancel, sharedS onChange={(e)=>{ let val = e.target.value; fmUtilsObj.setFileType(val); - openDir(fmUtilsObj.currPath); + openDir(fmUtilsObj.currPath, selectedSS); setFileType(val); }} options={fmUtilsObj.allowedFileTypes?.map((type)=>({ @@ -852,4 +852,5 @@ FileManager.propTypes = { onOK: PropTypes.func, onCancel: PropTypes.func, sharedStorages: PropTypes.array, + restrictedSharedStorage: PropTypes.array, }; diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index 5922c41d3..98a0e14f8 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -17,7 +17,7 @@ from threading import Lock import json from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT,\ - ALLOW_SAVE_PASSWORD + ALLOW_SAVE_PASSWORD, SHARED_STORAGE from werkzeug.user_agent import UserAgent from flask import Response, url_for, render_template, session, current_app from flask import request @@ -52,7 +52,7 @@ from pgadmin.tools.sqleditor.utils.macros import get_macros,\ get_user_macros, set_macros from pgadmin.utils.constants import MIMETYPE_APP_JS, \ SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, \ - ERROR_FETCHING_DATA, MY_STORAGE + ERROR_FETCHING_DATA, MY_STORAGE, ACCESS_DENIED_MESSAGE from pgadmin.model import Server, ServerGroup from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry from pgadmin.settings import get_setting @@ -1856,6 +1856,18 @@ def save_file(): last_storage = Preferences.module('file_manager').preference( 'last_storage').get() if last_storage != MY_STORAGE: + selectedDirList = [sdir for sdir in SHARED_STORAGE if + sdir['name'] == last_storage] + selectedDir = selectedDirList[0] if len( + selectedDirList) == 1 else None + + if selectedDir: + if selectedDir['restricted_access'] and \ + not current_user.has_role("Administrator"): + return make_json_response(success=0, + errormsg=ACCESS_DENIED_MESSAGE, + info='ACCESS_DENIED', + status=403) storage_manager_path = get_storage_directory( shared_storage=last_storage) else: diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx index 166cc3685..3372da21e 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx @@ -422,7 +422,9 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN }).catch((err)=>{ eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, err); }); - } else { + } else if(error.response?.status == 403 && error.response?.data.info == 'ACCESS_DENIED') { + Notifier.error(error.response.data.errormsg); + }else { let msg = parseApiError(error); eventBus.current.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, msg, true); eventBus.current.fireEvent(QUERY_TOOL_EVENTS.FOCUS_PANEL, PANELS.MESSAGES); diff --git a/web/pgadmin/utils/constants.py b/web/pgadmin/utils/constants.py index 400a44c82..61f147af8 100644 --- a/web/pgadmin/utils/constants.py +++ b/web/pgadmin/utils/constants.py @@ -122,3 +122,6 @@ PSYCOPG3 = 'psycopg3' # Shared storage MY_STORAGE = 'my_storage' +ACCESS_DENIED_MESSAGE = gettext( + "Access denied: You’re having limited access. You’re not allowed to " + "Rename, Delete or Create any files/folders") diff --git a/web/regression/javascript/file_manager/FileManager.spec.js b/web/regression/javascript/file_manager/FileManager.spec.js index 266977f89..63b60076d 100644 --- a/web/regression/javascript/file_manager/FileManager.spec.js +++ b/web/regression/javascript/file_manager/FileManager.spec.js @@ -42,7 +42,6 @@ const files = [ } } ]; - const transId = 140391; const configData = { 'transId': transId, @@ -87,6 +86,7 @@ const configData = { }; const sharedStorageConfig = ['Shared Storage']; +const restrictedSharedStorage = []; const params={ dialog_type: 'select_file', @@ -127,6 +127,7 @@ describe('FileManger', ()=>{ onOK={onOK} onCancel={onCancel} sharedStorages={sharedStorageConfig} + restrictedSharedStorage={restrictedSharedStorage} {...props} /> ); @@ -134,6 +135,7 @@ describe('FileManger', ()=>{ it('init', (done)=>{ networkMock.onPost('/file_manager/init').reply(200, {'data': configData}); + networkMock.onPost(`/file_manager/save_last_dir/${transId}`).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':null}); let ctrl = ctrlMount({}); setTimeout(()=>{ ctrl.update();