diff --git a/docs/en_US/master_password.rst b/docs/en_US/master_password.rst index 25f3435f9..936b82aa2 100644 --- a/docs/en_US/master_password.rst +++ b/docs/en_US/master_password.rst @@ -56,3 +56,5 @@ passwords. This is applicable only for desktop mode users. .. warning:: Resetting the master password will also remove all saved passwords and close all existing established connections. + +**Note:** pgAdmin 4 will use the OS password manager from version 7.2 onwards and fallback to master password if OS password manager is not available. diff --git a/requirements.txt b/requirements.txt index 7f5b54d2c..ee8d22966 100644 --- a/requirements.txt +++ b/requirements.txt @@ -53,4 +53,5 @@ azure-mgmt-subscription==3.1.1 azure-identity==1.12.0 google-api-python-client==2.* google-auth-oauthlib==1.0.0 -Werkzeug==2.2.3 \ No newline at end of file +Werkzeug==2.2.3 +keyring==23.* \ No newline at end of file diff --git a/web/pgAdmin4.py b/web/pgAdmin4.py index 00a980538..256e9ac27 100644 --- a/web/pgAdmin4.py +++ b/web/pgAdmin4.py @@ -12,6 +12,8 @@ a webserver, this will provide the WSGI interface, otherwise, we're going to start a web server.""" import sys +if sys.version_info <= (3, 9): + import select if sys.version_info < (3, 4): raise RuntimeError('This application must be run under Python 3.4 ' diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index eb895b01a..4f24552bb 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -17,8 +17,12 @@ from smtplib import SMTPConnectError, SMTPResponseException, \ SMTPAuthenticationError, SMTPSenderRefused, SMTPRecipientsRefused from socket import error as SOCKETErrorException from urllib.request import urlopen +from pgadmin.utils.constants import KEY_RING_SERVICE_NAME, \ + KEY_RING_USERNAME_FORMAT, KEY_RING_DESKTOP_USER import time + +import keyring from flask import current_app, render_template, url_for, make_response, \ flash, Response, request, after_this_request, redirect, session from flask_babel import gettext @@ -741,11 +745,13 @@ def get_nodes(): return make_json_response(data=nodes) -def form_master_password_response(existing=True, present=False, errmsg=None): +def form_master_password_response(existing=True, present=False, errmsg=None, + is_keyring=False): return make_json_response(data={ 'present': present, 'reset': existing, 'errmsg': errmsg, + 'is_keyring': is_keyring, 'is_error': True if errmsg else False }) @@ -804,6 +810,51 @@ def set_master_password(): if data != '': data = json.loads(data) + if not config.DISABLED_LOCAL_PASSWORD_STORAGE: + from pgadmin.model import Server + from pgadmin.utils.crypto import decrypt + desktop_user = current_user + try: + # pgAdmin will use the OS password manager to store the server + # password, here migrating the existing saved server password to + # OS password manager + if keyring.get_password( + KEY_RING_SERVICE_NAME, KEY_RING_DESKTOP_USER.format( + desktop_user.username)) or data['password']: + all_server = Server.query.all() + for server in all_server: + if server.password and data['password'] \ + and server.save_password: + name = KEY_RING_USERNAME_FORMAT.format(server.name, + server.id) + password = decrypt(server.password, + data['password']).decode() + # Store the password using OS password manager + keyring.set_password(KEY_RING_SERVICE_NAME, name, + password) + setattr(server, 'password', password) + + # Store the password using OS password manager + keyring.set_password(KEY_RING_SERVICE_NAME, + KEY_RING_DESKTOP_USER.format( + desktop_user.username), 'test') + return form_master_password_response( + existing=True, + present=True, + is_keyring=True + ) + else: + return form_master_password_response( + present=False, + is_keyring=True + ) + except Exception as e: + current_app.logger.warning( + 'Fail set password using OS password manager' + ', fallback to master password. Error: {0}'.format(e) + ) + config.DISABLED_LOCAL_PASSWORD_STORAGE = True + # Master password is not applicable for server mode # Enable master password if oauth is used if not config.SERVER_MODE or OAUTH2 in config.AUTHENTICATION_SOURCES \ diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 8ce34197d..a60fd2a8b 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -33,8 +33,12 @@ from pgadmin.utils.constants import UNAUTH_REQ, MIMETYPE_APP_JS, \ SERVER_CONNECTION_CLOSED from sqlalchemy import or_ from pgadmin.utils.preferences import Preferences +from pgadmin.utils.constants import KEY_RING_SERVICE_NAME, \ + KEY_RING_USERNAME_FORMAT, KEY_RING_TUNNEL_FORMAT from .... import socketio as sio +import keyring + def has_any(data, keys): """ @@ -714,6 +718,20 @@ class ServerNode(PGChildNodeView): server_name = s.name get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id) db.session.delete(s) + if not config.DISABLED_LOCAL_PASSWORD_STORAGE: + try: + server_name = KEY_RING_USERNAME_FORMAT.format( + s.name, + s.id) + # Get password form OS password manager + is_present = keyring.get_password( + KEY_RING_SERVICE_NAME, server_name) + # Delete saved password from OS password manager + if is_present: + keyring.delete_password(KEY_RING_SERVICE_NAME, + server_name) + except keyring.errors.KeyringError as e: + config.DISABLED_LOCAL_PASSWORD_STORAGE = True db.session.commit() self.delete_shared_server(server_name, gid, sid) QueryHistory.clear_history(current_user.id, sid) @@ -1052,10 +1070,11 @@ class ServerNode(PGChildNodeView): if data[item] == '': data[item] = None - # Get enc key - crypt_key_present, crypt_key = get_crypt_key() - if not crypt_key_present: - raise CryptKeyMissing + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + # Get enc key + crypt_key_present, crypt_key = get_crypt_key() + if not crypt_key_present: + raise CryptKeyMissing # Some fields can be provided with service file so they are optional if 'service' in data and not data['service']: @@ -1147,7 +1166,9 @@ class ServerNode(PGChildNodeView): # login with password have_password = True password = data['password'] - password = encrypt(password, crypt_key) + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + password = encrypt(password, crypt_key) + elif 'passfile' in data['connection_params'] and \ data['connection_params']['passfile'] != '': passfile = data['connection_params']['passfile'] @@ -1155,8 +1176,10 @@ class ServerNode(PGChildNodeView): if 'tunnel_password' in data and data["tunnel_password"] != '': have_tunnel_password = True tunnel_password = data['tunnel_password'] - tunnel_password = \ - encrypt(tunnel_password, crypt_key) + + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + tunnel_password = \ + encrypt(tunnel_password, crypt_key) status, errmsg = conn.connect( password=password, @@ -1177,15 +1200,31 @@ class ServerNode(PGChildNodeView): else: if 'save_password' in data and data['save_password'] and \ have_password and config.ALLOW_SAVE_PASSWORD: - setattr(server, 'password', password) - db.session.commit() + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + setattr(server, 'password', password) + db.session.commit() + else: + # Store the password using OS password manager + keyring.set_password( + KEY_RING_SERVICE_NAME, + KEY_RING_USERNAME_FORMAT.format(server.name, + server.id), + password) if 'save_tunnel_password' in data and \ data['save_tunnel_password'] and \ have_tunnel_password and \ config.ALLOW_SAVE_TUNNEL_PASSWORD: - setattr(server, 'tunnel_password', tunnel_password) - db.session.commit() + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + setattr(server, 'tunnel_password', tunnel_password) + db.session.commit() + else: + # Store the password using OS password manager + keyring.set_password( + KEY_RING_SERVICE_NAME, + KEY_RING_TUNNEL_FORMAT.format(server.name, + server.id), + tunnel_password) user = manager.user_info connected = True @@ -1379,18 +1418,29 @@ class ServerNode(PGChildNodeView): manager.update(server) conn = manager.connection() - # Get enc key - crypt_key_present, crypt_key = get_crypt_key() - if not crypt_key_present: - raise CryptKeyMissing + crypt_key = None + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + # Get enc key + crypt_key_present, crypt_key = get_crypt_key() + if not crypt_key_present: + raise CryptKeyMissing # If server using SSH Tunnel if server.use_ssh_tunnel: + if 'tunnel_password' not in data: - if server.tunnel_password is None: + if config.DISABLED_LOCAL_PASSWORD_STORAGE \ + and server.tunnel_password is None: prompt_tunnel_password = True else: - tunnel_password = server.tunnel_password + if not config.DISABLED_LOCAL_PASSWORD_STORAGE: + # Get password form OS password manager + tunnel_password = keyring.get_password( + KEY_RING_SERVICE_NAME, + KEY_RING_TUNNEL_FORMAT.format(server.name, + server.id)) + else: + tunnel_password = server.tunnel_password else: tunnel_password = data['tunnel_password'] \ if 'tunnel_password' in data else '' @@ -1400,9 +1450,16 @@ class ServerNode(PGChildNodeView): # Encrypt the password before saving with user's login # password key. try: - tunnel_password = encrypt(tunnel_password, crypt_key) \ - if tunnel_password is not None else \ - server.tunnel_password + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + tunnel_password = encrypt(tunnel_password, crypt_key) \ + if tunnel_password is not None else \ + server.tunnel_password + else: + # Get password form OS password manager + tunnel_password = keyring.get_password( + KEY_RING_SERVICE_NAME, + KEY_RING_TUNNEL_FORMAT.format(server.name, + server.id)) except Exception as e: current_app.logger.exception(e) return internal_server_error(errormsg=str(e)) @@ -1423,20 +1480,38 @@ class ServerNode(PGChildNodeView): elif passfile_param and passfile_param != '': passfile = passfile_param else: - password = conn_passwd or server.password + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + password = conn_passwd or server.password + else: + # Get password form OS password manager + password = keyring.get_password( + KEY_RING_SERVICE_NAME, + KEY_RING_USERNAME_FORMAT.format(server.name, + server.id)) else: password = data['password'] if 'password' in data else None save_password = data['save_password']\ if 'save_password' in data else False - # Encrypt the password before saving with user's login - # password key. - try: - password = encrypt(password, crypt_key) \ - if password is not None else server.password - except Exception as e: - current_app.logger.exception(e) - return internal_server_error(errormsg=str(e)) + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + try: + # Encrypt the password before saving with user's login + # password key. + password = encrypt(password, crypt_key) \ + if password is not None else server.password + except Exception as e: + current_app.logger.exception(e) + return internal_server_error(errormsg=str(e)) + elif save_password: + # Store the password using OS password manager + keyring.set_password( + KEY_RING_SERVICE_NAME, + KEY_RING_USERNAME_FORMAT.format( + server.name, server.id), password) + # Get password form OS password manager + password = keyring.get_password( + KEY_RING_SERVICE_NAME, + KEY_RING_USERNAME_FORMAT.format(server.name, server.id)) # Check do we need to prompt for the database server or ssh tunnel # password or both. Return the password template in case password is @@ -1486,7 +1561,7 @@ class ServerNode(PGChildNodeView): # Save the encrypted password using the user's login # password key, if there is any password to save - if password: + if password and config.SERVER_MODE: if server.shared and server.user_id != current_user.id: setattr(shared_server, 'password', password) else: @@ -1975,9 +2050,24 @@ class ServerNode(PGChildNodeView): server = ServerModule. \ get_shared_server_properties(server, shared_server) - if server.shared and server.user_id != current_user.id: - setattr(shared_server, 'save_password', None) + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + if server.shared and server.user_id != current_user.id: + setattr(shared_server, 'save_password', None) + else: + setattr(server, 'save_password', None) else: + try: + server_name = KEY_RING_USERNAME_FORMAT.format(server.name, + server.id) + # Get password form OS password manager + is_present = keyring.get_password(KEY_RING_SERVICE_NAME, + server_name) + if is_present: + # Delete saved password from OS password manager + keyring.delete_password(KEY_RING_SERVICE_NAME, + server_name) + except keyring.errors.KeyringError as e: + config.DISABLED_LOCAL_PASSWORD_STORAGE = True setattr(server, 'save_password', None) # If password was saved then clear the flag also @@ -2017,9 +2107,19 @@ class ServerNode(PGChildNodeView): success=0, info=self.not_found_error_msg() ) - - setattr(server, 'tunnel_password', None) - db.session.commit() + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + setattr(server, 'tunnel_password', None) + db.session.commit() + else: + server_name = KEY_RING_TUNNEL_FORMAT.format(server.name, + server.id) + # Get password form OS password manager + is_present = keyring.get_password(KEY_RING_SERVICE_NAME, + server_name) + if is_present: + # Delete saved password from OS password manager + keyring.delete_password(KEY_RING_SERVICE_NAME, server_name) + setattr(server, 'tunnel_password', None) except Exception as e: current_app.logger.error( "Unable to clear ssh tunnel password." diff --git a/web/pgadmin/browser/tests/test_master_password.py b/web/pgadmin/browser/tests/test_master_password.py index 179337684..8e84d5c75 100644 --- a/web/pgadmin/browser/tests/test_master_password.py +++ b/web/pgadmin/browser/tests/test_master_password.py @@ -80,6 +80,9 @@ class MasterPasswordTestCase(BaseTestGenerator): ) self.assertEqual(response.status_code, 200) + if not config.SERVER_MODE: + self.skipTest( + "This test is skipped on Desktop mode.") if hasattr(self, 'check_if_set'): response = self.tester.get( '/browser/master_password' diff --git a/web/pgadmin/evaluate_config.py b/web/pgadmin/evaluate_config.py index 1de7de11f..30e500231 100644 --- a/web/pgadmin/evaluate_config.py +++ b/web/pgadmin/evaluate_config.py @@ -9,6 +9,7 @@ import os import sys +import keyring # User configs loaded from config_local, config_distro etc. custom_config_settings = {} @@ -114,4 +115,13 @@ def evaluate_and_patch_config(config: dict) -> dict: # Enable PSQL in Desktop Mode. config['ENABLE_PSQL'] = True + if config.get('SERVER_MODE'): + config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', True) + else: + k_name = keyring.get_keyring().name + if k_name == 'fail Keyring': + config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', True) + else: + config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', False) + return config diff --git a/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx b/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx index 7bd6443e3..81d739fb9 100644 --- a/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx +++ b/web/pgadmin/static/js/Dialogs/MasterPasswordContent.jsx @@ -22,7 +22,7 @@ import { DefaultButton, PrimaryButton, PgIconButton } from '../components/Button import { useModalStyles } from '../helpers/ModalProvider'; import { FormFooterMessage, InputText, MESSAGE_TYPE } from '../components/FormComponents'; -export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data}) { +export default function MasterPasswordContent({ closeModal, onResetPassowrd, onOK, onCancel, setHeight, isPWDPresent, data, isKeyring}) { const classes = useModalStyles(); const containerRef = useRef(); const firstEleRef = useRef(); @@ -59,24 +59,44 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO return ( - - - - {isPWDPresent ? gettext('Please enter your master password.') : gettext('Please set a master password for pgAdmin.')} - -
- - {isPWDPresent ? gettext('This is required to unlock saved passwords and reconnect to the database server(s).') : gettext('This will be used to secure and later unlock saved passwords and other credentials.')} - + {isKeyring ? + + + + {gettext('Please enter your master password.')} + +
+ + {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 from version 7.2')} + +
+ + onTextChange(e, 'password')} onKeyDown={(e) => onKeyDown(e)}/> + + +
: + + + + {isPWDPresent ? gettext('Please enter your master password.') : gettext('Please set a master password for pgAdmin.')} + +
+ + {isPWDPresent ? gettext('This is required to unlock saved passwords and reconnect to the database server(s).') : gettext('This will be used to secure and later unlock saved passwords and other credentials.')} + +
+ + onTextChange(e, 'password')} onKeyDown={(e) => onKeyDown(e)}/> + +
- - onTextChange(e, 'password')} onKeyDown={(e) => onKeyDown(e)}/> - - -
+ } } onClick={() => { @@ -86,17 +106,19 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO window.open(_url, 'pgadmin_help'); }} > - {isPWDPresent && + {isPWDPresent && !isKeyring && } onClick={() => {onResetPassowrd?.();}} > {gettext('Reset Master Password')} } - } onClick={() => { - onCancel?.(); - closeModal(); - }} >{gettext('Cancel')} + { + !isKeyring && } onClick={() => { + onCancel?.(); + closeModal(); + }} >{gettext('Cancel')} + } } disabled={formData.password.length == 0} onClick={() => { @@ -121,4 +143,5 @@ MasterPasswordContent.propTypes = { setHeight: PropTypes.func, isPWDPresent: PropTypes.bool, data: PropTypes.object, + isKeyring: PropTypes.bool, }; diff --git a/web/pgadmin/static/js/Dialogs/index.jsx b/web/pgadmin/static/js/Dialogs/index.jsx index a3a4eaec0..de0bd6e87 100644 --- a/web/pgadmin/static/js/Dialogs/index.jsx +++ b/web/pgadmin/static/js/Dialogs/index.jsx @@ -160,7 +160,7 @@ export function checkMasterPassword(data, masterpass_callback_queue, cancel_call const api = getApiInstance(); api.post(url_for('browser.set_master_password'), data).then((res)=> { if(!res.data.data.present) { - showMasterPassword(res.data.data.reset, res.data.data.errmsg, masterpass_callback_queue, cancel_callback); + showMasterPassword(res.data.data.reset, res.data.data.errmsg, masterpass_callback_queue, cancel_callback, res.data.data.is_keyring); } else { masterPassCallbacks(masterpass_callback_queue); } @@ -170,7 +170,7 @@ 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) { +export function showMasterPassword(isPWDPresent, errmsg, masterpass_callback_queue, cancel_callback, is_keyring) { const api = getApiInstance(); let title = isPWDPresent ? gettext('Unlock Saved Passwords') : gettext('Set Master Password'); @@ -179,6 +179,7 @@ export function showMasterPassword(isPWDPresent, errmsg, masterpass_callback_que { setNewSize(pgAdmin.Browser.stdW.md, containerHeight); }} diff --git a/web/pgadmin/utils/constants.py b/web/pgadmin/utils/constants.py index 5d25975a4..c6f4da9fb 100644 --- a/web/pgadmin/utils/constants.py +++ b/web/pgadmin/utils/constants.py @@ -126,3 +126,9 @@ 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") + + +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}' diff --git a/web/pgadmin/utils/driver/psycopg3/connection.py b/web/pgadmin/utils/driver/psycopg3/connection.py index 2aec287b5..4e5826b20 100644 --- a/web/pgadmin/utils/driver/psycopg3/connection.py +++ b/web/pgadmin/utils/driver/psycopg3/connection.py @@ -263,10 +263,19 @@ class Connection(BaseConnection): return True, None manager = self.manager - crypt_key_present, crypt_key = get_crypt_key() - password, encpass, is_update_password = self._check_user_password( - kwargs) + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + crypt_key_present, crypt_key = get_crypt_key() + + if not crypt_key_present: + raise CryptKeyMissing() + + password, encpass, is_update_password = self._check_user_password( + kwargs) + else: + password = None + encpass = kwargs['password'] if 'password' in kwargs else None + is_update_password = True passfile = kwargs['passfile'] if 'passfile' in kwargs else None tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \ @@ -292,13 +301,15 @@ class Connection(BaseConnection): if self.reconnecting is not False: self.password = None - if not crypt_key_present: - raise CryptKeyMissing() - - is_error, errmsg, password = self._decode_password(encpass, manager, - password, crypt_key) - if is_error: - return False, errmsg + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + is_error, errmsg, password = self._decode_password(encpass, + manager, + password, + crypt_key) + if is_error: + return False, errmsg + else: + password = encpass # If no password credential is found then connect request might # come from Query tool, ViewData grid, debugger etc tools. @@ -657,7 +668,7 @@ WHERE db.datname = current_database()""") def __cursor(self, server_cursor=False, scrollable=False): - if not get_crypt_key()[0]: + if not get_crypt_key()[0] and config.SERVER_MODE: raise CryptKeyMissing() # Check SSH Tunnel is alive or not. If used by the database @@ -1547,13 +1558,13 @@ Failed to reset the connection to the server due to following error: user = User.query.filter_by(id=current_user.id).first() if user is None: return False, self.UNAUTHORIZED_REQUEST + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + crypt_key_present, crypt_key = get_crypt_key() + if not crypt_key_present: + return False, crypt_key - crypt_key_present, crypt_key = get_crypt_key() - if not crypt_key_present: - return False, crypt_key - - password = decrypt(password, crypt_key)\ - .decode() + password = decrypt(password, crypt_key)\ + .decode() try: with ConnectionLocker(self.manager.kerberos_conn): diff --git a/web/pgadmin/utils/driver/psycopg3/server_manager.py b/web/pgadmin/utils/driver/psycopg3/server_manager.py index 68be36b5c..56f0e3570 100644 --- a/web/pgadmin/utils/driver/psycopg3/server_manager.py +++ b/web/pgadmin/utils/driver/psycopg3/server_manager.py @@ -30,6 +30,9 @@ from pgadmin.utils.master_password import get_crypt_key from pgadmin.utils.exception import ObjectGone from pgadmin.utils.passexec import PasswordExec from psycopg.conninfo import make_conninfo +import keyring +from pgadmin.utils.constants import KEY_RING_SERVICE_NAME, \ + KEY_RING_USERNAME_FORMAT, KEY_RING_TUNNEL_FORMAT if config.SUPPORT_SSH_TUNNEL: from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError @@ -78,6 +81,7 @@ class ServerManager(object): self.db_info = dict() self.server_types = None self.db_res = server.db_res + self.name = server.name self.passexec = \ PasswordExec(server.passexec_cmd, server.passexec_expiration) \ if server.passexec_cmd else None @@ -232,7 +236,8 @@ WHERE db.oid = {0}""".format(did)) "Could not find the specified database." )) - if not get_crypt_key()[0]: + if not get_crypt_key()[0] and ( + config.SERVER_MODE or config.DISABLED_LOCAL_PASSWORD_STORAGE): # the reason its not connected might be missing key raise CryptKeyMissing() @@ -529,11 +534,14 @@ WHERE db.oid = {0}""".format(did)) def export_password_env(self, env): if self.password: - crypt_key_present, crypt_key = get_crypt_key() - if not crypt_key_present: - return False, crypt_key + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + crypt_key_present, crypt_key = get_crypt_key() + if not crypt_key_present: + return False, crypt_key + password = decrypt(self.password, crypt_key).decode() + else: + password = self.password - password = decrypt(self.password, crypt_key).decode() os.environ[str(env)] = password def create_ssh_tunnel(self, tunnel_password): @@ -549,15 +557,23 @@ WHERE db.oid = {0}""".format(did)) return False, gettext("Unauthorized request.") if tunnel_password is not None and tunnel_password != '': - crypt_key_present, crypt_key = get_crypt_key() - if not crypt_key_present: - raise CryptKeyMissing() + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + crypt_key_present, crypt_key = get_crypt_key() + if not crypt_key_present: + raise CryptKeyMissing() try: - tunnel_password = decrypt(tunnel_password, crypt_key) - # password is in bytes, for python3 we need it in string - if isinstance(tunnel_password, bytes): - tunnel_password = tunnel_password.decode() + if config.DISABLED_LOCAL_PASSWORD_STORAGE: + tunnel_password = decrypt(tunnel_password, crypt_key) + # password is in bytes, for python3 we need it in string + if isinstance(tunnel_password, bytes): + tunnel_password = tunnel_password.decode() + else: + # Get password form OS password manager + tunnel_password = keyring.get_password( + KEY_RING_SERVICE_NAME, + KEY_RING_TUNNEL_FORMAT.format(self.manager.name, + self.manager.sid)) except Exception as e: current_app.logger.exception(e)