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();