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}'