diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index 1aeb20a2c..4e8ba50dc 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -746,12 +746,12 @@ def get_nodes(): def form_master_password_response(existing=True, present=False, errmsg=None, - is_keyring=False): + keyring_name=''): return make_json_response(data={ 'present': present, 'reset': existing, 'errmsg': errmsg, - 'is_keyring': is_keyring, + 'keyring_name': keyring_name, 'is_error': True if errmsg else False }) @@ -821,7 +821,7 @@ def set_master_password(): not validate_master_password(data.get('password')): return form_master_password_response( present=False, - is_keyring=True, + keyring_name=config.KEYRING_NAME, errmsg=gettext("Incorrect master password") ) from pgadmin.model import Server @@ -835,6 +835,7 @@ def set_master_password(): KEY_RING_SERVICE_NAME, KEY_RING_DESKTOP_USER.format( desktop_user.username)) or data['password']: all_server = Server.query.all() + is_migrated = False for server in all_server: if server.password and data['password'] \ and server.save_password: @@ -845,6 +846,7 @@ def set_master_password(): # Store the password using OS password manager keyring.set_password(KEY_RING_SERVICE_NAME, name, password) + is_migrated = True setattr(server, 'password', None) db.session.commit() @@ -856,12 +858,12 @@ def set_master_password(): return form_master_password_response( existing=True, present=True, - is_keyring=True + keyring_name=config.KEYRING_NAME if is_migrated else '' ) else: return form_master_password_response( present=False, - is_keyring=True + keyring_name=config.KEYRING_NAME ) except Exception as e: current_app.logger.warning( diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index a60fd2a8b..bcbbf13ab 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -802,6 +802,10 @@ class ServerNode(PGChildNodeView): data = request.form if request.form else json.loads( request.data ) + + old_server_name = '' + if 'name' in data: + old_server_name = server.name if 'db_res' in data: data['db_res'] = ','.join(data['db_res']) @@ -834,6 +838,25 @@ class ServerNode(PGChildNodeView): ) try: + if len(old_server_name) and old_server_name != server.name and \ + not config.DISABLED_LOCAL_PASSWORD_STORAGE: + # If server name is changed then update keyring with + # new server name + password = keyring.get_password( + KEY_RING_SERVICE_NAME, + KEY_RING_USERNAME_FORMAT.format(old_server_name, + server.id)) + + keyring.set_password( + KEY_RING_SERVICE_NAME, + KEY_RING_USERNAME_FORMAT.format(server.name, server.id), + password) + + server_name = KEY_RING_USERNAME_FORMAT.format( + old_server_name, server.id) + # Delete saved password from OS password manager + keyring.delete_password(KEY_RING_SERVICE_NAME, + server_name) db.session.commit() except Exception as e: current_app.logger.exception(e) diff --git a/web/pgadmin/evaluate_config.py b/web/pgadmin/evaluate_config.py index 30e500231..76d5461b0 100644 --- a/web/pgadmin/evaluate_config.py +++ b/web/pgadmin/evaluate_config.py @@ -117,11 +117,14 @@ def evaluate_and_patch_config(config: dict) -> dict: if config.get('SERVER_MODE'): config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', True) + config.setdefault('KEYRING_NAME', '') else: k_name = keyring.get_keyring().name if k_name == 'fail Keyring': config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', True) + config.setdefault('KEYRING_NAME', '') else: config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', False) + config.setdefault('KEYRING_NAME', k_name) return config diff --git a/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx b/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx index a1a479bac..585ad066d 100644 --- a/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx +++ b/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx @@ -20,13 +20,14 @@ import HelpIcon from '@material-ui/icons/Help'; import { DefaultButton, PrimaryButton, PgIconButton } from '../components/Buttons'; import { useModalStyles } from '../helpers/ModalProvider'; -import { FormFooterMessage, InputText, MESSAGE_TYPE } from '../components/FormComponents'; +import { FormFooterMessage, FormNote, InputText, MESSAGE_TYPE } from '../components/FormComponents'; -export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data, isKeyring}) { +export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data, keyringName}) { const classes = useModalStyles(); const containerRef = useRef(); const firstEleRef = useRef(); const okBtnRef = useRef(); + const isKeyring = keyringName.length > 0; const [formData, setFormData] = useState({ password: '' }); @@ -67,7 +68,7 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO
- {gettext('This is required to migrate the existing saved Server password and SSH tunnel password to OS password manager, as pgAdmin 4 will now use the OS password manager in Desktop mode.')} + @@ -143,5 +144,5 @@ MasterPasswordContent.propTypes = { setHeight: PropTypes.func, isPWDPresent: PropTypes.bool, data: PropTypes.object, - isKeyring: PropTypes.bool, + keyringName: PropTypes.string, }; diff --git a/web/pgadmin/static/js/Dialogs/index.jsx b/web/pgadmin/static/js/Dialogs/index.jsx index 5ccdc3976..b6c96816e 100644 --- a/web/pgadmin/static/js/Dialogs/index.jsx +++ b/web/pgadmin/static/js/Dialogs/index.jsx @@ -156,10 +156,16 @@ function masterPassCallbacks(masterpass_callback_queue) { export function checkMasterPassword(data, masterpass_callback_queue, cancel_callback) { const api = getApiInstance(); api.post(url_for('browser.set_master_password'), data).then((res)=> { + let isKeyring = res.data.data.keyring_name.length > 0; if(!res.data.data.present) { - showMasterPassword(res.data.data.reset, res.data.data.errmsg, masterpass_callback_queue, cancel_callback, res.data.data.is_keyring); + showMasterPassword(res.data.data.reset, res.data.data.errmsg, masterpass_callback_queue, cancel_callback, res.data.data.keyring_name); } else { masterPassCallbacks(masterpass_callback_queue); + + if(isKeyring) { + Notify.alert(gettext('Migration successful'), + gettext(`Passwords previously saved by pgAdmin have been successfully migrated to ${res.data.data.keyring_name} and removed from the pgAdmin store.`)); + } } }).catch(function(error) { Notify.pgRespErrorNotify(error); @@ -167,16 +173,16 @@ export function checkMasterPassword(data, masterpass_callback_queue, cancel_call } // This functions is used to show the master password dialog. -export function showMasterPassword(isPWDPresent, errmsg, masterpass_callback_queue, cancel_callback, is_keyring) { +export function showMasterPassword(isPWDPresent, errmsg, masterpass_callback_queue, cancel_callback, keyring_name) { const api = getApiInstance(); - let title = isPWDPresent ? gettext('Unlock Saved Passwords') : gettext('Set Master Password'); + let title = keyring_name.length > 0 ? gettext('Migrate Saved Passwords') : isPWDPresent ? gettext('Unlock Saved Passwords') : gettext('Set Master Password'); mountDialog(title, (onClose, setNewSize)=> { return { setNewSize(pgAdmin.Browser.stdW.md, containerHeight); }} diff --git a/web/pgadmin/static/js/Theme/dark.js b/web/pgadmin/static/js/Theme/dark.js index 38d8faede..7b31959c1 100644 --- a/web/pgadmin/static/js/Theme/dark.js +++ b/web/pgadmin/static/js/Theme/dark.js @@ -85,6 +85,7 @@ export default function(basicSettings) { borderColor: '#4a4a4a', inputBorderColor: '#6b6b6b', inputDisabledBg: 'inherit', + errorColor: '#DA6758', headerBg: '#424242', activeBorder: '#d4d4d4', activeColor: '#d4d4d4', diff --git a/web/pgadmin/static/js/Theme/high_contrast.js b/web/pgadmin/static/js/Theme/high_contrast.js index da80388fd..6bb96b464 100644 --- a/web/pgadmin/static/js/Theme/high_contrast.js +++ b/web/pgadmin/static/js/Theme/high_contrast.js @@ -83,6 +83,7 @@ export default function(basicSettings) { borderColor: '#A6B7C8', inputBorderColor: '#6b6b6b', inputDisabledBg: '#1F2932', + errorColor: '#DA6758', headerBg: '#010B15', activeBorder: '#fff', activeColor: '#fff', diff --git a/web/pgadmin/static/js/Theme/standard.js b/web/pgadmin/static/js/Theme/standard.js index 7c055c239..674b034ba 100644 --- a/web/pgadmin/static/js/Theme/standard.js +++ b/web/pgadmin/static/js/Theme/standard.js @@ -89,6 +89,7 @@ export default function(basicSettings) { backgroundColor: fade('#000', 0.65), color: '#fff', }, + errorColor: '#E53935', inputBorderColor: '#dde0e6', inputDisabledBg: '#f3f5f9', headerBg: '#fff', diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx index 997a38d3f..9bac8cf79 100644 --- a/web/pgadmin/static/js/helpers/ModalProvider.jsx +++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx @@ -260,7 +260,7 @@ export const useModalStyles = makeStyles((theme) => ({ iconButtonStyle: { marginLeft: 'auto', marginRight: '4px' - } + }, })); function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH, showTitle=true }) { diff --git a/web/pgadmin/utils/constants.py b/web/pgadmin/utils/constants.py index c6f4da9fb..4d65bfef4 100644 --- a/web/pgadmin/utils/constants.py +++ b/web/pgadmin/utils/constants.py @@ -128,7 +128,7 @@ ACCESS_DENIED_MESSAGE = gettext( "Rename, Delete or Create any files/folders") -KEY_RING_SERVICE_NAME = 'pgAdmin4-Masterpass-Service' -KEY_RING_USERNAME_FORMAT = 'pgAdmin4-{0}-{1}' -KEY_RING_TUNNEL_FORMAT = 'pgAdmin4-tunnel-{0}-{1}' -KEY_RING_DESKTOP_USER = 'desktop-user-{0}' +KEY_RING_SERVICE_NAME = 'pgAdmin4' +KEY_RING_USERNAME_FORMAT = KEY_RING_SERVICE_NAME + '-{0}-{1}' +KEY_RING_TUNNEL_FORMAT = KEY_RING_SERVICE_NAME + '-tunnel-{0}-{1}' +KEY_RING_DESKTOP_USER = KEY_RING_SERVICE_NAME + '-desktop-user-{0}'