mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Revamp the current password saving implementation to keyring and reducing repeated OS user password prompts. #7076
The new implementation will store the master password in the keyring instead of storing each and every server password separately. The master password will be used to encrypt/decrypt server password when storing in the pgAdmin config DB.
This commit is contained in:
@@ -583,7 +583,7 @@ ALLOW_SAVE_TUNNEL_PASSWORD = False
|
|||||||
# Applicable for desktop mode only
|
# Applicable for desktop mode only
|
||||||
##########################################################################
|
##########################################################################
|
||||||
MASTER_PASSWORD_REQUIRED = True
|
MASTER_PASSWORD_REQUIRED = True
|
||||||
|
USE_OS_SECRET_STORAGE = True
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
# pgAdmin encrypts the database connection and ssh tunnel password using a
|
# pgAdmin encrypts the database connection and ssh tunnel password using a
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from pgadmin.utils.ajax import make_json_response, internal_server_error
|
|||||||
from pgadmin.authenticate.internal import BaseAuthentication
|
from pgadmin.authenticate.internal import BaseAuthentication
|
||||||
from pgadmin.authenticate import get_auth_sources
|
from pgadmin.authenticate import get_auth_sources
|
||||||
from pgadmin.utils.csrf import pgCSRFProtect
|
from pgadmin.utils.csrf import pgCSRFProtect
|
||||||
|
from pgadmin.utils.master_password import set_crypt_key
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import gssapi
|
import gssapi
|
||||||
@@ -193,7 +193,8 @@ class KerberosAuthentication(BaseAuthentication):
|
|||||||
if status:
|
if status:
|
||||||
# Saving the first 15 characters of the kerberos key
|
# Saving the first 15 characters of the kerberos key
|
||||||
# to encrypt/decrypt database password
|
# to encrypt/decrypt database password
|
||||||
session['pass_enc_key'] = auth_header[1][0:15]
|
pass_enc_key = auth_header[1][0:15]
|
||||||
|
set_crypt_key(pass_enc_key)
|
||||||
# Create user
|
# Create user
|
||||||
retval = self.__auto_create_user(
|
retval = self.__auto_create_user(
|
||||||
str(negotiate.initiator_name))
|
str(negotiate.initiator_name))
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from pgadmin.utils import PgAdminModule, get_safe_post_login_redirect, \
|
|||||||
get_safe_post_logout_redirect
|
get_safe_post_logout_redirect
|
||||||
from pgadmin.utils.csrf import pgCSRFProtect
|
from pgadmin.utils.csrf import pgCSRFProtect
|
||||||
from pgadmin.model import db
|
from pgadmin.model import db
|
||||||
|
from pgadmin.utils.master_password import set_crypt_key
|
||||||
|
|
||||||
OAUTH2_LOGOUT = 'oauth2.logout'
|
OAUTH2_LOGOUT = 'oauth2.logout'
|
||||||
OAUTH2_AUTHORIZE = 'oauth2.authorize'
|
OAUTH2_AUTHORIZE = 'oauth2.authorize'
|
||||||
@@ -210,7 +211,8 @@ class OAuth2Authentication(BaseAuthentication):
|
|||||||
session['oauth2_token'] = self.oauth2_clients[
|
session['oauth2_token'] = self.oauth2_clients[
|
||||||
self.oauth2_current_client].authorize_access_token()
|
self.oauth2_current_client].authorize_access_token()
|
||||||
|
|
||||||
session['pass_enc_key'] = session['oauth2_token']['access_token']
|
pass_enc_key = session['oauth2_token']['access_token']
|
||||||
|
set_crypt_key(pass_enc_key)
|
||||||
|
|
||||||
if 'OAUTH2_LOGOUT_URL' in self.oauth2_config[
|
if 'OAUTH2_LOGOUT_URL' in self.oauth2_config[
|
||||||
self.oauth2_current_client]:
|
self.oauth2_current_client]:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
import config
|
import config
|
||||||
from flask import request, current_app, session, Response, render_template, \
|
from flask import request, current_app, Response, render_template, \
|
||||||
url_for
|
url_for
|
||||||
from flask_babel import gettext
|
from flask_babel import gettext
|
||||||
from flask_security import login_user
|
from flask_security import login_user
|
||||||
@@ -23,6 +23,7 @@ from pgadmin.utils.constants import WEBSERVER
|
|||||||
from pgadmin.utils import PgAdminModule
|
from pgadmin.utils import PgAdminModule
|
||||||
from pgadmin.utils.csrf import pgCSRFProtect
|
from pgadmin.utils.csrf import pgCSRFProtect
|
||||||
from flask_security.utils import logout_user
|
from flask_security.utils import logout_user
|
||||||
|
from pgadmin.utils.master_password import set_crypt_key
|
||||||
|
|
||||||
|
|
||||||
class WebserverModule(PgAdminModule):
|
class WebserverModule(PgAdminModule):
|
||||||
@@ -89,8 +90,9 @@ class WebserverAuthentication(BaseAuthentication):
|
|||||||
return False, gettext(
|
return False, gettext(
|
||||||
"Webserver authenticate failed.")
|
"Webserver authenticate failed.")
|
||||||
|
|
||||||
session['pass_enc_key'] = ''.join(
|
pass_enc_key = ''.join(
|
||||||
(secrets.choice(string.ascii_lowercase) for _ in range(10)))
|
(secrets.choice(string.ascii_lowercase) for _ in range(10)))
|
||||||
|
set_crypt_key(pass_enc_key)
|
||||||
useremail = request.environ.get('mail')
|
useremail = request.environ.get('mail')
|
||||||
if not useremail:
|
if not useremail:
|
||||||
useremail = ''
|
useremail = ''
|
||||||
|
|||||||
@@ -10,22 +10,21 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import secrets
|
||||||
import sys
|
import sys
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from smtplib import SMTPConnectError, SMTPResponseException, \
|
from smtplib import SMTPConnectError, SMTPResponseException, \
|
||||||
SMTPServerDisconnected, SMTPDataError, SMTPHeloError, SMTPException, \
|
SMTPServerDisconnected, SMTPDataError, SMTPHeloError, SMTPException, \
|
||||||
SMTPAuthenticationError, SMTPSenderRefused, SMTPRecipientsRefused
|
SMTPAuthenticationError, SMTPSenderRefused, SMTPRecipientsRefused
|
||||||
from socket import error as SOCKETErrorException
|
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, KEY_RING_TUNNEL_FORMAT, \
|
|
||||||
MessageType
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
import keyring
|
import keyring
|
||||||
|
from keyring.errors import KeyringLocked
|
||||||
|
from pgadmin.utils.constants import KEY_RING_SERVICE_NAME, \
|
||||||
|
KEY_RING_USER_NAME,MessageType
|
||||||
|
|
||||||
from flask import current_app, render_template, url_for, make_response, \
|
from flask import current_app, render_template, url_for, make_response, \
|
||||||
flash, Response, request, after_this_request, redirect, session
|
flash, Response, request, redirect, session
|
||||||
from flask_babel import gettext
|
from flask_babel import gettext
|
||||||
from libgravatar import Gravatar
|
from libgravatar import Gravatar
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
@@ -44,22 +43,24 @@ from werkzeug.datastructures import MultiDict
|
|||||||
import config
|
import config
|
||||||
from pgadmin import current_blueprint
|
from pgadmin import current_blueprint
|
||||||
from pgadmin.authenticate import get_logout_url
|
from pgadmin.authenticate import get_logout_url
|
||||||
from pgadmin.authenticate.mfa.utils import mfa_required, is_mfa_enabled
|
from pgadmin.authenticate.mfa.utils import is_mfa_enabled
|
||||||
from pgadmin.settings import get_setting, store_setting
|
from pgadmin.settings import get_setting
|
||||||
from pgadmin.utils import PgAdminModule
|
from pgadmin.utils import PgAdminModule
|
||||||
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
|
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
|
||||||
bad_request
|
bad_request
|
||||||
from pgadmin.utils.csrf import pgCSRFProtect
|
from pgadmin.utils.csrf import pgCSRFProtect
|
||||||
from pgadmin.utils.preferences import Preferences
|
from pgadmin.utils.preferences import Preferences
|
||||||
from pgadmin.utils.menu import MenuItem
|
|
||||||
from pgadmin.browser.register_browser_preferences import \
|
from pgadmin.browser.register_browser_preferences import \
|
||||||
register_browser_preferences
|
register_browser_preferences
|
||||||
from pgadmin.utils.master_password import validate_master_password, \
|
from pgadmin.utils.master_password import validate_master_password, \
|
||||||
set_masterpass_check_text, cleanup_master_password, get_crypt_key, \
|
set_masterpass_check_text, cleanup_master_password, get_crypt_key, \
|
||||||
set_crypt_key, process_masterpass_disabled
|
set_crypt_key, process_masterpass_disabled, \
|
||||||
|
delete_local_storage_master_key, \
|
||||||
|
get_master_password_key_from_os_secret, \
|
||||||
|
get_master_password_from_master_hook
|
||||||
from pgadmin.model import User, db
|
from pgadmin.model import User, db
|
||||||
from pgadmin.utils.constants import MIMETYPE_APP_JS, PGADMIN_NODE,\
|
from pgadmin.utils.constants import MIMETYPE_APP_JS, PGADMIN_NODE, \
|
||||||
INTERNAL, KERBEROS, LDAP, QT_DEFAULT_PLACEHOLDER, OAUTH2, WEBSERVER,\
|
INTERNAL, KERBEROS, LDAP, QT_DEFAULT_PLACEHOLDER, OAUTH2, WEBSERVER, \
|
||||||
VW_EDT_DEFAULT_PLACEHOLDER
|
VW_EDT_DEFAULT_PLACEHOLDER
|
||||||
from pgadmin.authenticate import AuthSourceManager
|
from pgadmin.authenticate import AuthSourceManager
|
||||||
from pgadmin.utils.exception import CryptKeyMissing
|
from pgadmin.utils.exception import CryptKeyMissing
|
||||||
@@ -74,7 +75,7 @@ PGADMIN_BROWSER = 'pgAdmin.Browser'
|
|||||||
PASS_ERROR_MSG = gettext('Your password has not been changed.')
|
PASS_ERROR_MSG = gettext('Your password has not been changed.')
|
||||||
SMTP_SOCKET_ERROR = gettext(
|
SMTP_SOCKET_ERROR = gettext(
|
||||||
'SMTP Socket error: {error}\n {pass_error}').format(
|
'SMTP Socket error: {error}\n {pass_error}').format(
|
||||||
error={}, pass_error=PASS_ERROR_MSG)
|
error={}, pass_error=PASS_ERROR_MSG)
|
||||||
SMTP_ERROR = gettext('SMTP error: {error}\n {pass_error}').format(
|
SMTP_ERROR = gettext('SMTP error: {error}\n {pass_error}').format(
|
||||||
error={}, pass_error=PASS_ERROR_MSG)
|
error={}, pass_error=PASS_ERROR_MSG)
|
||||||
PASS_ERROR = gettext('Error: {error}\n {pass_error}').format(
|
PASS_ERROR = gettext('Error: {error}\n {pass_error}').format(
|
||||||
@@ -631,14 +632,13 @@ def get_nodes():
|
|||||||
|
|
||||||
|
|
||||||
def form_master_password_response(existing=True, present=False, errmsg=None,
|
def form_master_password_response(existing=True, present=False, errmsg=None,
|
||||||
keyring_name='',
|
keyring_name='', master_password_hook=''):
|
||||||
invalid_master_password_hook=False):
|
|
||||||
return make_json_response(data={
|
return make_json_response(data={
|
||||||
'present': present,
|
'present': present,
|
||||||
'reset': existing,
|
'reset': existing,
|
||||||
'errmsg': errmsg,
|
'errmsg': errmsg,
|
||||||
'keyring_name': keyring_name,
|
'keyring_name': keyring_name,
|
||||||
'invalid_master_password_hook': invalid_master_password_hook,
|
'master_password_hook': master_password_hook,
|
||||||
'is_error': True if errmsg else False
|
'is_error': True if errmsg else False
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -673,14 +673,11 @@ def reset_master_password():
|
|||||||
Removes the master password and remove all saved passwords
|
Removes the master password and remove all saved passwords
|
||||||
This password will be used to encrypt/decrypt saved server passwords
|
This password will be used to encrypt/decrypt saved server passwords
|
||||||
"""
|
"""
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
|
||||||
# This is to set the Desktop user password so it will not ask for
|
|
||||||
# migrate exiting passwords as those are getting cleared
|
|
||||||
keyring.set_password(KEY_RING_SERVICE_NAME,
|
|
||||||
KEY_RING_DESKTOP_USER.format(
|
|
||||||
current_user.username), 'test')
|
|
||||||
cleanup_master_password()
|
cleanup_master_password()
|
||||||
status, crypt_key = get_crypt_key()
|
status, crypt_key = get_crypt_key()
|
||||||
|
if not status and config.MASTER_PASSWORD_HOOK:
|
||||||
|
crypt_key = get_master_password_from_master_hook()
|
||||||
|
|
||||||
# Set masterpass_check if MASTER_PASSWORD_HOOK is set which provides
|
# Set masterpass_check if MASTER_PASSWORD_HOOK is set which provides
|
||||||
# encryption key
|
# encryption key
|
||||||
if config.MASTER_PASSWORD_REQUIRED and config.MASTER_PASSWORD_HOOK:
|
if config.MASTER_PASSWORD_REQUIRED and config.MASTER_PASSWORD_HOOK:
|
||||||
@@ -695,9 +692,7 @@ def set_master_password():
|
|||||||
Set the master password and store in the memory
|
Set the master password and store in the memory
|
||||||
This password will be used to encrypt/decrypt saved server passwords
|
This password will be used to encrypt/decrypt saved server passwords
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
if request.form:
|
if request.form:
|
||||||
data = request.form
|
data = request.form
|
||||||
elif request.data:
|
elif request.data:
|
||||||
@@ -708,130 +703,105 @@ def set_master_password():
|
|||||||
if data != '':
|
if data != '':
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
|
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE and \
|
keyring_name = ''
|
||||||
(config.ALLOW_SAVE_PASSWORD or config.ALLOW_SAVE_TUNNEL_PASSWORD):
|
errmsg = ''
|
||||||
if data.get('password') and config.MASTER_PASSWORD_REQUIRED and\
|
if not config.SERVER_MODE:
|
||||||
not validate_master_password(data.get('password')):
|
if config.USE_OS_SECRET_STORAGE:
|
||||||
return form_master_password_response(
|
try:
|
||||||
present=False,
|
# Try to get master key is from local os storage
|
||||||
keyring_name=config.KEYRING_NAME,
|
master_key = get_master_password_key_from_os_secret()
|
||||||
errmsg=gettext("Incorrect master password")
|
master_password = data.get('password', None)
|
||||||
)
|
keyring_name = config.KEYRING_NAME
|
||||||
from pgadmin.model import Server
|
if not master_key:
|
||||||
from pgadmin.utils.crypto import decrypt
|
# Generate new one and migration required
|
||||||
desktop_user = current_user
|
master_key = secrets.token_urlsafe(12)
|
||||||
|
|
||||||
enc_key = data['password']
|
# migrate existing server passwords
|
||||||
if not config.MASTER_PASSWORD_REQUIRED:
|
from pgadmin.browser.server_groups.servers.utils \
|
||||||
status, enc_key = get_crypt_key()
|
import migrate_saved_passwords
|
||||||
if not status:
|
migrated_save_passwords, error = migrate_saved_passwords(
|
||||||
raise CryptKeyMissing
|
master_key, master_password)
|
||||||
|
|
||||||
try:
|
if migrated_save_passwords:
|
||||||
all_server = Server.query.all()
|
# Update keyring
|
||||||
saved_password_servers = [server for server in all_server if
|
|
||||||
server.save_password]
|
|
||||||
# pgAdmin will use the OS password manager to store the server
|
|
||||||
# password, here migrating the existing saved server password to
|
|
||||||
# OS password manager
|
|
||||||
if len(saved_password_servers) > 0 and (keyring.get_password(
|
|
||||||
KEY_RING_SERVICE_NAME, KEY_RING_DESKTOP_USER.format(
|
|
||||||
desktop_user.username)) or enc_key):
|
|
||||||
is_migrated = False
|
|
||||||
|
|
||||||
for server in saved_password_servers:
|
|
||||||
if enc_key:
|
|
||||||
if server.password and config.ALLOW_SAVE_PASSWORD:
|
|
||||||
name = KEY_RING_USERNAME_FORMAT.format(server.name,
|
|
||||||
server.id)
|
|
||||||
password = decrypt(server.password,
|
|
||||||
enc_key).decode()
|
|
||||||
# Store the password using OS password manager
|
|
||||||
keyring.set_password(KEY_RING_SERVICE_NAME, name,
|
|
||||||
password)
|
|
||||||
is_migrated = True
|
|
||||||
setattr(server, 'password', None)
|
|
||||||
|
|
||||||
if server.tunnel_password and \
|
|
||||||
config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
|
||||||
tname = KEY_RING_TUNNEL_FORMAT.format(server.name,
|
|
||||||
server.id)
|
|
||||||
tpassword = decrypt(server.tunnel_password,
|
|
||||||
enc_key).decode()
|
|
||||||
# Store the password using OS password manager
|
|
||||||
keyring.set_password(KEY_RING_SERVICE_NAME, tname,
|
|
||||||
tpassword)
|
|
||||||
is_migrated = True
|
|
||||||
setattr(server, 'tunnel_password', None)
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# 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,
|
|
||||||
keyring_name=config.KEYRING_NAME if is_migrated else ''
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if len(all_server) == 0:
|
|
||||||
# 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(
|
|
||||||
present=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
is_master_password_present = True
|
|
||||||
keyring_name = ''
|
|
||||||
for server in all_server:
|
|
||||||
is_password_present = \
|
|
||||||
server.save_password or server.tunnel_password
|
|
||||||
if server.password and is_password_present:
|
|
||||||
is_master_password_present = False
|
|
||||||
keyring_name = config.KEYRING_NAME
|
|
||||||
break
|
|
||||||
|
|
||||||
if is_master_password_present:
|
|
||||||
# Store the password using OS password manager
|
|
||||||
keyring.set_password(KEY_RING_SERVICE_NAME,
|
keyring.set_password(KEY_RING_SERVICE_NAME,
|
||||||
KEY_RING_DESKTOP_USER.format(
|
KEY_RING_USER_NAME,
|
||||||
desktop_user.username),
|
master_key)
|
||||||
'test')
|
# set crypt key
|
||||||
|
set_crypt_key(master_key)
|
||||||
|
return form_master_password_response(
|
||||||
|
existing=True,
|
||||||
|
present=True,
|
||||||
|
keyring_name=keyring_name)
|
||||||
|
else:
|
||||||
|
if not error:
|
||||||
|
set_crypt_key(master_key)
|
||||||
|
return form_master_password_response(
|
||||||
|
present=True)
|
||||||
|
# Migration failed
|
||||||
|
elif error != 'Master password required':
|
||||||
|
errmsg = error
|
||||||
|
return form_master_password_response(
|
||||||
|
existing=False,
|
||||||
|
present=True,
|
||||||
|
errmsg=errmsg,
|
||||||
|
keyring_name=keyring_name)
|
||||||
|
else:
|
||||||
|
current_app.logger.warning(
|
||||||
|
' Master key was already present in the keyring,'
|
||||||
|
' hence not doing any migration')
|
||||||
|
# Key is already generated and set, no migration required
|
||||||
|
# set crypt key
|
||||||
|
set_crypt_key(master_key)
|
||||||
return form_master_password_response(
|
return form_master_password_response(
|
||||||
present=is_master_password_present,
|
present=True)
|
||||||
keyring_name=keyring_name
|
except KeyringLocked as e:
|
||||||
)
|
current_app.logger.warning(
|
||||||
except Exception as e:
|
'Failed to set because Access Denied.'
|
||||||
current_app.logger.warning(
|
' Error: {0}'.format(e))
|
||||||
'Fail set password using OS password manager'
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
', fallback to master password. Error: {0}'.format(e)
|
except Exception as e:
|
||||||
)
|
current_app.logger.warning(
|
||||||
config.DISABLED_LOCAL_PASSWORD_STORAGE = True
|
'Failed to set encryption key using OS password manager'
|
||||||
|
', fallback to master password. Error: {0}'.format(e))
|
||||||
# If the master password is required and the master password hook
|
# Also if masterpass_check is none it means previously
|
||||||
# is specified then try to retrieve the encryption key and update data.
|
# passwords were migrated using keyring crypt key.
|
||||||
# If there is an error while retrieving it, return an error message.
|
# Reset all passwords because we are going to master password
|
||||||
if config.SERVER_MODE and config.MASTER_PASSWORD_REQUIRED and \
|
# again and while setting master password, all server
|
||||||
config.MASTER_PASSWORD_HOOK:
|
# passwords are decrypted using old key before re-encryption
|
||||||
status, enc_key = get_crypt_key()
|
if current_user.masterpass_check is None:
|
||||||
if status:
|
from pgadmin.browser.server_groups.servers.utils \
|
||||||
data = {'password': enc_key, 'submit_password': True}
|
import remove_saved_passwords, update_session_manager
|
||||||
|
remove_saved_passwords(current_user.id)
|
||||||
|
update_session_manager(current_user.id)
|
||||||
|
# Disable local os storage if any exception while creation
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
delete_local_storage_master_key()
|
||||||
else:
|
else:
|
||||||
error = gettext('The master password could not be retrieved from '
|
# if os secret storage disabled now, but was used once then
|
||||||
'the MASTER_PASSWORD_HOOK utility specified {0}.'
|
# remove all the saved passwords
|
||||||
'Please check that the hook utility is configured'
|
delete_local_storage_master_key()
|
||||||
' correctly.'.format(config.MASTER_PASSWORD_HOOK))
|
else:
|
||||||
return form_master_password_response(
|
# If the master password is required and the master password hook
|
||||||
existing=False,
|
# is specified then try to retrieve the encryption key and update data.
|
||||||
present=False,
|
# If there is an error while retrieving it, return an error message.
|
||||||
errmsg=error,
|
if config.MASTER_PASSWORD_REQUIRED and config.MASTER_PASSWORD_HOOK:
|
||||||
invalid_master_password_hook=True
|
master_password = get_master_password_from_master_hook()
|
||||||
)
|
if master_password:
|
||||||
|
data = {'password': master_password, 'submit_password': True}
|
||||||
|
else:
|
||||||
|
errmsg = gettext(
|
||||||
|
'The master password could not be retrieved from the'
|
||||||
|
' MASTER_PASSWORD_HOOK utility specified {0}. Please check'
|
||||||
|
' that the hook utility is configured correctly.'.format(
|
||||||
|
config.MASTER_PASSWORD_HOOK))
|
||||||
|
return form_master_password_response(
|
||||||
|
existing=False,
|
||||||
|
present=False,
|
||||||
|
errmsg=errmsg,
|
||||||
|
master_password_hook=config.MASTER_PASSWORD_HOOK,
|
||||||
|
keyring_name=keyring_name
|
||||||
|
)
|
||||||
|
|
||||||
# Master password is applicable for Desktop mode and in server mode
|
# Master password is applicable for Desktop mode and in server mode
|
||||||
# only when auth sources are oauth, kerberos, webserver.
|
# only when auth sources are oauth, kerberos, webserver.
|
||||||
@@ -843,20 +813,16 @@ def set_master_password():
|
|||||||
if current_user.masterpass_check is not None and \
|
if current_user.masterpass_check is not None and \
|
||||||
data.get('submit_password', False) and \
|
data.get('submit_password', False) and \
|
||||||
not validate_master_password(data.get('password')):
|
not validate_master_password(data.get('password')):
|
||||||
errmsg = '' if config.MASTER_PASSWORD_HOOK \
|
|
||||||
else gettext("Incorrect master password")
|
|
||||||
invalid_master_password_hook = \
|
|
||||||
True if config.MASTER_PASSWORD_HOOK else False
|
|
||||||
return form_master_password_response(
|
return form_master_password_response(
|
||||||
existing=True,
|
existing=True,
|
||||||
present=False,
|
present=False,
|
||||||
errmsg=errmsg,
|
errmsg=errmsg,
|
||||||
invalid_master_password_hook=invalid_master_password_hook
|
master_password_hook=config.MASTER_PASSWORD_HOOK,
|
||||||
|
keyring_name=keyring_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# if master password received in request
|
# if master password received in request
|
||||||
if data != '' and data.get('password', '') != '':
|
if data != '' and data.get('password', '') != '':
|
||||||
|
|
||||||
# store the master pass in the memory
|
# store the master pass in the memory
|
||||||
set_crypt_key(data.get('password'))
|
set_crypt_key(data.get('password'))
|
||||||
|
|
||||||
@@ -864,7 +830,6 @@ def set_master_password():
|
|||||||
# master check is not set, which means the server password
|
# master check is not set, which means the server password
|
||||||
# data is old and is encrypted with old key
|
# data is old and is encrypted with old key
|
||||||
# Re-encrypt with new key
|
# Re-encrypt with new key
|
||||||
|
|
||||||
from pgadmin.browser.server_groups.servers.utils \
|
from pgadmin.browser.server_groups.servers.utils \
|
||||||
import reencrpyt_server_passwords
|
import reencrpyt_server_passwords
|
||||||
reencrpyt_server_passwords(
|
reencrpyt_server_passwords(
|
||||||
@@ -877,13 +842,14 @@ def set_master_password():
|
|||||||
|
|
||||||
# If password in request is empty then try to get it with
|
# If password in request is empty then try to get it with
|
||||||
# get_crypt_key method. If get_crypt_key() returns false status and
|
# get_crypt_key method. If get_crypt_key() returns false status and
|
||||||
# masterpass_check is already set, provide a pop to enter
|
# masterpass_check is already set, provide a popup to enter
|
||||||
# master password(present) without the reset option.(existing).
|
# master password(present) without the reset option.(existing).
|
||||||
elif not get_crypt_key()[0] and \
|
elif not get_crypt_key()[0] and \
|
||||||
current_user.masterpass_check is not None:
|
current_user.masterpass_check is not None:
|
||||||
return form_master_password_response(
|
return form_master_password_response(
|
||||||
existing=True,
|
existing=True,
|
||||||
present=False,
|
present=False,
|
||||||
|
keyring_name=keyring_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# If get_crypt_key return True,but crypt_key is none and
|
# If get_crypt_key return True,but crypt_key is none and
|
||||||
@@ -896,7 +862,8 @@ def set_master_password():
|
|||||||
return form_master_password_response(
|
return form_master_password_response(
|
||||||
existing=False,
|
existing=False,
|
||||||
present=False,
|
present=False,
|
||||||
errmsg=error_message
|
errmsg=error_message,
|
||||||
|
keyring_name=keyring_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# if master password is disabled now, but was used once then
|
# if master password is disabled now, but was used once then
|
||||||
@@ -904,7 +871,6 @@ def set_master_password():
|
|||||||
process_masterpass_disabled()
|
process_masterpass_disabled()
|
||||||
|
|
||||||
if config.SERVER_MODE and current_user.masterpass_check is None:
|
if config.SERVER_MODE and current_user.masterpass_check is None:
|
||||||
|
|
||||||
crypt_key = get_crypt_key()[1]
|
crypt_key = get_crypt_key()[1]
|
||||||
from pgadmin.browser.server_groups.servers.utils \
|
from pgadmin.browser.server_groups.servers.utils \
|
||||||
import reencrpyt_server_passwords
|
import reencrpyt_server_passwords
|
||||||
@@ -921,8 +887,6 @@ def set_master_password():
|
|||||||
# Only register route if SECURITY_CHANGEABLE is set to True
|
# Only register route if SECURITY_CHANGEABLE is set to True
|
||||||
# We can't access app context here so cannot
|
# We can't access app context here so cannot
|
||||||
# use app.config['SECURITY_CHANGEABLE']
|
# use app.config['SECURITY_CHANGEABLE']
|
||||||
|
|
||||||
|
|
||||||
if hasattr(config, 'SECURITY_CHANGEABLE') and config.SECURITY_CHANGEABLE:
|
if hasattr(config, 'SECURITY_CHANGEABLE') and config.SECURITY_CHANGEABLE:
|
||||||
@blueprint.route("/change_password", endpoint="change_password",
|
@blueprint.route("/change_password", endpoint="change_password",
|
||||||
methods=['GET', 'POST'])
|
methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -38,11 +38,8 @@ from pgadmin.utils.constants import UNAUTH_REQ, MIMETYPE_APP_JS, \
|
|||||||
SERVER_CONNECTION_CLOSED
|
SERVER_CONNECTION_CLOSED
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from pgadmin.utils.preferences import Preferences
|
from pgadmin.utils.preferences import Preferences
|
||||||
from pgadmin.utils.constants import KEY_RING_SERVICE_NAME, \
|
|
||||||
KEY_RING_USERNAME_FORMAT, KEY_RING_TUNNEL_FORMAT, KEY_RING_DESKTOP_USER
|
|
||||||
from .... import socketio as sio
|
from .... import socketio as sio
|
||||||
from pgadmin.utils import get_complete_file_path
|
from pgadmin.utils import get_complete_file_path
|
||||||
import keyring
|
|
||||||
|
|
||||||
|
|
||||||
def has_any(data, keys):
|
def has_any(data, keys):
|
||||||
@@ -255,22 +252,6 @@ class ServerModule(sg.ServerGroupPluginModule):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.exception(e)
|
current_app.logger.exception(e)
|
||||||
errmsg = str(e)
|
errmsg = str(e)
|
||||||
|
|
||||||
is_password_saved = bool(server.save_password)
|
|
||||||
is_tunnel_password_saved = bool(server.tunnel_password)
|
|
||||||
|
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
|
||||||
sname = KEY_RING_USERNAME_FORMAT.format(server.name, server.id)
|
|
||||||
spassword = keyring.get_password(
|
|
||||||
KEY_RING_SERVICE_NAME, sname)
|
|
||||||
|
|
||||||
is_password_saved = bool(spassword)
|
|
||||||
tunnelname = KEY_RING_TUNNEL_FORMAT.format(server.name,
|
|
||||||
server.id)
|
|
||||||
tunnel_password = keyring.get_password(KEY_RING_SERVICE_NAME,
|
|
||||||
tunnelname)
|
|
||||||
is_tunnel_password_saved = bool(tunnel_password)
|
|
||||||
|
|
||||||
yield self.generate_browser_node(
|
yield self.generate_browser_node(
|
||||||
"%d" % (server.id),
|
"%d" % (server.id),
|
||||||
gid,
|
gid,
|
||||||
@@ -287,8 +268,8 @@ class ServerModule(sg.ServerGroupPluginModule):
|
|||||||
wal_pause=wal_paused,
|
wal_pause=wal_paused,
|
||||||
host=server.host,
|
host=server.host,
|
||||||
port=server.port,
|
port=server.port,
|
||||||
is_password_saved=is_password_saved,
|
is_password_saved=bool(server.save_password),
|
||||||
is_tunnel_password_saved=is_tunnel_password_saved,
|
is_tunnel_password_saved=bool(server.tunnel_password),
|
||||||
was_connected=was_connected,
|
was_connected=was_connected,
|
||||||
errmsg=errmsg,
|
errmsg=errmsg,
|
||||||
user_id=server.user_id,
|
user_id=server.user_id,
|
||||||
@@ -605,22 +586,6 @@ class ServerNode(PGChildNodeView):
|
|||||||
manager.release()
|
manager.release()
|
||||||
errmsg = "{0} : {1}".format(server.name, result)
|
errmsg = "{0} : {1}".format(server.name, result)
|
||||||
|
|
||||||
is_password_saved = bool(server.save_password)
|
|
||||||
is_tunnel_password_saved = bool(server.tunnel_password)
|
|
||||||
|
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
|
||||||
sname = KEY_RING_USERNAME_FORMAT.format(server.name, server.id)
|
|
||||||
spassword = keyring.get_password(
|
|
||||||
KEY_RING_SERVICE_NAME, sname)
|
|
||||||
|
|
||||||
is_password_saved = bool(spassword)
|
|
||||||
|
|
||||||
tunnelname = KEY_RING_TUNNEL_FORMAT.format(server.name,
|
|
||||||
server.id)
|
|
||||||
tunnel_password = keyring.get_password(KEY_RING_SERVICE_NAME,
|
|
||||||
tunnelname)
|
|
||||||
is_tunnel_password_saved = bool(tunnel_password)
|
|
||||||
|
|
||||||
res.append(
|
res.append(
|
||||||
self.blueprint.generate_browser_node(
|
self.blueprint.generate_browser_node(
|
||||||
"%d" % (server.id),
|
"%d" % (server.id),
|
||||||
@@ -637,8 +602,8 @@ class ServerNode(PGChildNodeView):
|
|||||||
user=manager.user_info if connected else None,
|
user=manager.user_info if connected else None,
|
||||||
in_recovery=in_recovery,
|
in_recovery=in_recovery,
|
||||||
wal_pause=wal_paused,
|
wal_pause=wal_paused,
|
||||||
is_password_saved=is_password_saved,
|
is_password_saved=bool(server.save_password),
|
||||||
is_tunnel_password_saved=is_tunnel_password_saved,
|
is_tunnel_password_saved=bool(server.tunnel_password),
|
||||||
errmsg=errmsg,
|
errmsg=errmsg,
|
||||||
username=server.username,
|
username=server.username,
|
||||||
shared=server.shared,
|
shared=server.shared,
|
||||||
@@ -761,20 +726,6 @@ class ServerNode(PGChildNodeView):
|
|||||||
server_name = s.name
|
server_name = s.name
|
||||||
get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id)
|
get_driver(PG_DEFAULT_DRIVER).delete_manager(s.id)
|
||||||
db.session.delete(s)
|
db.session.delete(s)
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
|
||||||
try:
|
|
||||||
sname = KEY_RING_USERNAME_FORMAT.format(
|
|
||||||
s.name,
|
|
||||||
s.id)
|
|
||||||
# Get password form OS password manager
|
|
||||||
is_present = keyring.get_password(
|
|
||||||
KEY_RING_SERVICE_NAME, sname)
|
|
||||||
# Delete saved password from OS password manager
|
|
||||||
if is_present:
|
|
||||||
keyring.delete_password(KEY_RING_SERVICE_NAME,
|
|
||||||
sname)
|
|
||||||
except keyring.errors.KeyringError as e:
|
|
||||||
config.DISABLED_LOCAL_PASSWORD_STORAGE = True
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
self.delete_shared_server(server_name, gid, sid)
|
self.delete_shared_server(server_name, gid, sid)
|
||||||
QueryHistory.clear_history(current_user.id, sid)
|
QueryHistory.clear_history(current_user.id, sid)
|
||||||
@@ -888,26 +839,6 @@ class ServerNode(PGChildNodeView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(old_server_name) and old_server_name != server.name and \
|
|
||||||
not config.DISABLED_LOCAL_PASSWORD_STORAGE and \
|
|
||||||
server.save_password:
|
|
||||||
# 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()
|
db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.exception(e)
|
current_app.logger.exception(e)
|
||||||
@@ -1175,11 +1106,10 @@ class ServerNode(PGChildNodeView):
|
|||||||
if data[item] == '':
|
if data[item] == '':
|
||||||
data[item] = None
|
data[item] = None
|
||||||
|
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
# Get enc key
|
||||||
# Get enc key
|
crypt_key_present, crypt_key = get_crypt_key()
|
||||||
crypt_key_present, crypt_key = get_crypt_key()
|
if not crypt_key_present:
|
||||||
if not crypt_key_present:
|
raise CryptKeyMissing
|
||||||
raise CryptKeyMissing
|
|
||||||
|
|
||||||
# Some fields can be provided with service file so they are optional
|
# Some fields can be provided with service file so they are optional
|
||||||
if 'service' in data and not data['service']:
|
if 'service' in data and not data['service']:
|
||||||
@@ -1276,9 +1206,7 @@ class ServerNode(PGChildNodeView):
|
|||||||
# login with password
|
# login with password
|
||||||
have_password = True
|
have_password = True
|
||||||
password = data['password']
|
password = data['password']
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
password = encrypt(password, crypt_key)
|
||||||
password = encrypt(password, crypt_key)
|
|
||||||
|
|
||||||
elif 'passfile' in data['connection_params'] and \
|
elif 'passfile' in data['connection_params'] and \
|
||||||
data['connection_params']['passfile'] != '':
|
data['connection_params']['passfile'] != '':
|
||||||
passfile = data['connection_params']['passfile']
|
passfile = data['connection_params']['passfile']
|
||||||
@@ -1286,10 +1214,7 @@ class ServerNode(PGChildNodeView):
|
|||||||
if 'tunnel_password' in data and data["tunnel_password"] != '':
|
if 'tunnel_password' in data and data["tunnel_password"] != '':
|
||||||
have_tunnel_password = True
|
have_tunnel_password = True
|
||||||
tunnel_password = data['tunnel_password']
|
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(
|
status, errmsg = conn.connect(
|
||||||
password=password,
|
password=password,
|
||||||
@@ -1310,32 +1235,15 @@ class ServerNode(PGChildNodeView):
|
|||||||
else:
|
else:
|
||||||
if 'save_password' in data and data['save_password'] and \
|
if 'save_password' in data and data['save_password'] and \
|
||||||
have_password and config.ALLOW_SAVE_PASSWORD:
|
have_password and config.ALLOW_SAVE_PASSWORD:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
setattr(server, 'password', password)
|
||||||
setattr(server, 'password', password)
|
db.session.commit()
|
||||||
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 \
|
if 'save_tunnel_password' in data and \
|
||||||
data['save_tunnel_password'] and \
|
data['save_tunnel_password'] and \
|
||||||
have_tunnel_password and \
|
have_tunnel_password and \
|
||||||
config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
setattr(server, 'tunnel_password', tunnel_password)
|
||||||
setattr(server, 'tunnel_password', tunnel_password)
|
db.session.commit()
|
||||||
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)
|
|
||||||
tunnel_password_saved = True
|
|
||||||
|
|
||||||
replication_type = get_replication_type(conn,
|
replication_type = get_replication_type(conn,
|
||||||
manager.version)
|
manager.version)
|
||||||
@@ -1540,39 +1448,18 @@ class ServerNode(PGChildNodeView):
|
|||||||
conn = manager.connection()
|
conn = manager.connection()
|
||||||
|
|
||||||
crypt_key = None
|
crypt_key = None
|
||||||
if server.save_password:
|
# Get enc key
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE or \
|
crypt_key_present, crypt_key = get_crypt_key()
|
||||||
not keyring.get_password(
|
if not crypt_key_present:
|
||||||
KEY_RING_SERVICE_NAME,
|
raise CryptKeyMissing
|
||||||
KEY_RING_DESKTOP_USER.format(current_user.username)):
|
|
||||||
crypt_key_present, crypt_key = get_crypt_key()
|
|
||||||
if not crypt_key_present:
|
|
||||||
raise CryptKeyMissing
|
|
||||||
|
|
||||||
else:
|
|
||||||
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 using SSH Tunnel
|
||||||
if server.use_ssh_tunnel:
|
if server.use_ssh_tunnel:
|
||||||
|
|
||||||
if 'tunnel_password' not in data:
|
if 'tunnel_password' not in data:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE \
|
if server.tunnel_password is None:
|
||||||
and server.tunnel_password is None:
|
|
||||||
prompt_tunnel_password = True
|
prompt_tunnel_password = True
|
||||||
else:
|
else:
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
tunnel_password = server.tunnel_password
|
||||||
# Get password form OS password manager
|
|
||||||
tunnel_password = keyring.get_password(
|
|
||||||
KEY_RING_SERVICE_NAME,
|
|
||||||
KEY_RING_TUNNEL_FORMAT.format(server.name,
|
|
||||||
server.id))
|
|
||||||
prompt_tunnel_password = not bool(tunnel_password)
|
|
||||||
else:
|
|
||||||
tunnel_password = server.tunnel_password
|
|
||||||
else:
|
else:
|
||||||
tunnel_password = data['tunnel_password'] \
|
tunnel_password = data['tunnel_password'] \
|
||||||
if 'tunnel_password' in data else ''
|
if 'tunnel_password' in data else ''
|
||||||
@@ -1582,11 +1469,9 @@ class ServerNode(PGChildNodeView):
|
|||||||
# Encrypt the password before saving with user's login
|
# Encrypt the password before saving with user's login
|
||||||
# password key.
|
# password key.
|
||||||
try:
|
try:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
tunnel_password = encrypt(tunnel_password, crypt_key) \
|
||||||
tunnel_password = encrypt(tunnel_password, crypt_key) \
|
if tunnel_password is not None else \
|
||||||
if tunnel_password is not None else \
|
server.tunnel_password
|
||||||
server.tunnel_password
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.exception(e)
|
current_app.logger.exception(e)
|
||||||
return internal_server_error(errormsg=str(e))
|
return internal_server_error(errormsg=str(e))
|
||||||
@@ -1608,43 +1493,20 @@ class ServerNode(PGChildNodeView):
|
|||||||
get_complete_file_path(passfile_param):
|
get_complete_file_path(passfile_param):
|
||||||
passfile = passfile_param
|
passfile = passfile_param
|
||||||
else:
|
else:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
password = conn_passwd or server.password
|
||||||
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))
|
|
||||||
prompt_password = (
|
|
||||||
True
|
|
||||||
if password is None and server.passexec_cmd is None
|
|
||||||
else False
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
password = data['password'] if 'password' in data else None
|
password = data['password'] if 'password' in data else None
|
||||||
save_password = data['save_password']\
|
save_password = data['save_password']\
|
||||||
if 'save_password' in data else False
|
if 'save_password' in data else False
|
||||||
|
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
try:
|
||||||
try:
|
# Encrypt the password before saving with user's login
|
||||||
# Encrypt the password before saving with user's login
|
# password key.
|
||||||
# password key.
|
password = encrypt(password, crypt_key) \
|
||||||
password = encrypt(password, crypt_key) \
|
if password is not None else server.password
|
||||||
if password is not None else server.password
|
except Exception as e:
|
||||||
except Exception as e:
|
current_app.logger.exception(e)
|
||||||
current_app.logger.exception(e)
|
return internal_server_error(errormsg=str(e))
|
||||||
return internal_server_error(errormsg=str(e))
|
|
||||||
elif save_password and config.ALLOW_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
|
# Check do we need to prompt for the database server or ssh tunnel
|
||||||
# password or both. Return the password template in case password is
|
# password or both. Return the password template in case password is
|
||||||
@@ -1691,7 +1553,7 @@ class ServerNode(PGChildNodeView):
|
|||||||
|
|
||||||
# Save the encrypted password using the user's login
|
# Save the encrypted password using the user's login
|
||||||
# password key, if there is any password to save
|
# password key, if there is any password to save
|
||||||
if password and config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
if password:
|
||||||
if server.shared and server.user_id != current_user.id:
|
if server.shared and server.user_id != current_user.id:
|
||||||
setattr(shared_server, 'password', password)
|
setattr(shared_server, 'password', password)
|
||||||
else:
|
else:
|
||||||
@@ -1707,18 +1569,8 @@ class ServerNode(PGChildNodeView):
|
|||||||
|
|
||||||
if save_tunnel_password and config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
if save_tunnel_password and config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
||||||
try:
|
try:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
# Save the encrypted tunnel password.
|
||||||
# Save the encrypted tunnel password.
|
setattr(server, 'tunnel_password', tunnel_password)
|
||||||
setattr(server, 'tunnel_password', tunnel_password)
|
|
||||||
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)
|
|
||||||
setattr(server, 'tunnel_password', None)
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Release Connection
|
# Release Connection
|
||||||
@@ -1881,28 +1733,16 @@ class ServerNode(PGChildNodeView):
|
|||||||
elif request.data:
|
elif request.data:
|
||||||
data = json.loads(request.data)
|
data = json.loads(request.data)
|
||||||
|
|
||||||
crypt_key = None
|
# Get enc key
|
||||||
|
crypt_key_present, crypt_key = get_crypt_key()
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
if not crypt_key_present:
|
||||||
# Get enc key
|
raise CryptKeyMissing
|
||||||
crypt_key_present, crypt_key = get_crypt_key()
|
|
||||||
if not crypt_key_present:
|
|
||||||
raise CryptKeyMissing
|
|
||||||
|
|
||||||
# Fetch Server Details
|
# Fetch Server Details
|
||||||
server = Server.query.filter_by(id=sid).first()
|
server = Server.query.filter_by(id=sid).first()
|
||||||
|
|
||||||
if server is None:
|
if server is None:
|
||||||
return bad_request(self.not_found_error_msg())
|
return bad_request(self.not_found_error_msg())
|
||||||
|
|
||||||
spassword = None
|
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE and \
|
|
||||||
bool(server.save_password):
|
|
||||||
sname = KEY_RING_USERNAME_FORMAT.format(server.name,
|
|
||||||
server.id)
|
|
||||||
spassword = keyring.get_password(
|
|
||||||
KEY_RING_SERVICE_NAME, sname)
|
|
||||||
|
|
||||||
# Fetch User Details.
|
# Fetch User Details.
|
||||||
user = User.query.filter_by(id=current_user.id).first()
|
user = User.query.filter_by(id=current_user.id).first()
|
||||||
if user is None:
|
if user is None:
|
||||||
@@ -1914,9 +1754,9 @@ class ServerNode(PGChildNodeView):
|
|||||||
|
|
||||||
# If there is no password found for the server
|
# If there is no password found for the server
|
||||||
# then check for pgpass file
|
# then check for pgpass file
|
||||||
if (not server.password or spassword) and \
|
if not server.password and not manager.password and \
|
||||||
not manager.password and hasattr(server, 'connection_params') \
|
hasattr(server, 'connection_params') and \
|
||||||
and 'passfile' in server.connection_params and \
|
'passfile' in server.connection_params and \
|
||||||
manager.get_connection_param_value('passfile') and \
|
manager.get_connection_param_value('passfile') and \
|
||||||
server.connection_params['passfile'] == \
|
server.connection_params['passfile'] == \
|
||||||
manager.get_connection_param_value('passfile'):
|
manager.get_connection_param_value('passfile'):
|
||||||
@@ -1956,17 +1796,9 @@ class ServerNode(PGChildNodeView):
|
|||||||
|
|
||||||
# Check against old password only if no pgpass file
|
# Check against old password only if no pgpass file
|
||||||
if not is_passfile:
|
if not is_passfile:
|
||||||
if not config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
decrypted_password = decrypt(manager.password, crypt_key)
|
||||||
if spassword:
|
if isinstance(decrypted_password, bytes):
|
||||||
decrypted_password = spassword
|
decrypted_password = decrypted_password.decode()
|
||||||
else:
|
|
||||||
decrypted_password = manager.password
|
|
||||||
else:
|
|
||||||
decrypted_password = decrypt(manager.password, crypt_key)
|
|
||||||
|
|
||||||
if isinstance(decrypted_password, bytes):
|
|
||||||
decrypted_password = decrypted_password.decode()
|
|
||||||
|
|
||||||
password = data['password']
|
password = data['password']
|
||||||
|
|
||||||
# Validate old password before setting new.
|
# Validate old password before setting new.
|
||||||
@@ -1998,17 +1830,7 @@ class ServerNode(PGChildNodeView):
|
|||||||
|
|
||||||
# Store password in sqlite only if no pgpass file
|
# Store password in sqlite only if no pgpass file
|
||||||
if not is_passfile:
|
if not is_passfile:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
password = encrypt(data['newPassword'], crypt_key)
|
||||||
password = encrypt(data['newPassword'], crypt_key)
|
|
||||||
elif not config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
|
||||||
if config.ALLOW_SAVE_PASSWORD and bool(
|
|
||||||
server.save_password):
|
|
||||||
keyring.set_password(
|
|
||||||
KEY_RING_SERVICE_NAME,
|
|
||||||
KEY_RING_USERNAME_FORMAT.format(server.name,
|
|
||||||
server.id),
|
|
||||||
data['newPassword'])
|
|
||||||
password = data['newPassword']
|
|
||||||
# Check if old password was stored in pgadmin4 sqlite database.
|
# Check if old password was stored in pgadmin4 sqlite database.
|
||||||
# If yes then update that password.
|
# If yes then update that password.
|
||||||
if server.password is not None and config.ALLOW_SAVE_PASSWORD:
|
if server.password is not None and config.ALLOW_SAVE_PASSWORD:
|
||||||
@@ -2232,25 +2054,10 @@ class ServerNode(PGChildNodeView):
|
|||||||
server = ServerModule. \
|
server = ServerModule. \
|
||||||
get_shared_server_properties(server, shared_server)
|
get_shared_server_properties(server, shared_server)
|
||||||
|
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
if server.shared and server.user_id != current_user.id:
|
||||||
if server.shared and server.user_id != current_user.id:
|
setattr(shared_server, 'password', None)
|
||||||
setattr(shared_server, 'password', None)
|
|
||||||
else:
|
|
||||||
setattr(server, 'password', None)
|
|
||||||
else:
|
else:
|
||||||
try:
|
setattr(server, 'password', None)
|
||||||
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
|
# If password was saved then clear the flag also
|
||||||
# 0 is False in SQLite db
|
# 0 is False in SQLite db
|
||||||
@@ -2289,19 +2096,8 @@ class ServerNode(PGChildNodeView):
|
|||||||
success=0,
|
success=0,
|
||||||
info=self.not_found_error_msg()
|
info=self.not_found_error_msg()
|
||||||
)
|
)
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
setattr(server, 'tunnel_password', None)
|
||||||
setattr(server, 'tunnel_password', None)
|
db.session.commit()
|
||||||
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:
|
except Exception as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
"Unable to clear ssh tunnel password."
|
"Unable to clear ssh tunnel password."
|
||||||
|
|||||||
@@ -9,12 +9,20 @@
|
|||||||
|
|
||||||
"""Server helper utilities"""
|
"""Server helper utilities"""
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
|
import keyring
|
||||||
|
from flask_login import current_user
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
from pgadmin.utils.constants import KEY_RING_USERNAME_FORMAT, \
|
||||||
|
KEY_RING_SERVICE_NAME, KEY_RING_USER_NAME, KEY_RING_TUNNEL_FORMAT, \
|
||||||
|
KEY_RING_DESKTOP_USER
|
||||||
from pgadmin.utils.crypto import encrypt, decrypt
|
from pgadmin.utils.crypto import encrypt, decrypt
|
||||||
import config
|
import config
|
||||||
from pgadmin.model import db, Server
|
from pgadmin.model import db, Server
|
||||||
|
from flask import current_app
|
||||||
|
from pgadmin.utils.exception import CryptKeyMissing
|
||||||
|
from pgadmin.utils.master_password import validate_master_password, \
|
||||||
|
get_crypt_key, set_masterpass_check_text
|
||||||
|
|
||||||
|
|
||||||
def is_valid_ipaddress(address):
|
def is_valid_ipaddress(address):
|
||||||
@@ -232,6 +240,157 @@ def _password_check(server, manager, old_key, new_key):
|
|||||||
manager.password = password
|
manager.password = password
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_passwords_from_os_secret_storage(servers, enc_key):
|
||||||
|
"""
|
||||||
|
Migrate password stored in os secret storage
|
||||||
|
:param servers: server list
|
||||||
|
:param enc_key: new encryption key
|
||||||
|
:return: True if successful else False
|
||||||
|
"""
|
||||||
|
passwords_migrated = False
|
||||||
|
error = ''
|
||||||
|
try:
|
||||||
|
if len(servers) > 0:
|
||||||
|
for server in servers:
|
||||||
|
server_name = KEY_RING_USERNAME_FORMAT.format(server.name,
|
||||||
|
server.id)
|
||||||
|
server_password = keyring.get_password(
|
||||||
|
KEY_RING_SERVICE_NAME, server_name)
|
||||||
|
if server_password:
|
||||||
|
server_password = encrypt(server_password, enc_key)
|
||||||
|
setattr(server, 'password', server_password)
|
||||||
|
else:
|
||||||
|
setattr(server, 'save_password', 0)
|
||||||
|
|
||||||
|
tunnel_name = KEY_RING_TUNNEL_FORMAT.format(server.name,
|
||||||
|
server.id)
|
||||||
|
tunnel_password = keyring.get_password(
|
||||||
|
KEY_RING_SERVICE_NAME, tunnel_name)
|
||||||
|
if tunnel_password:
|
||||||
|
setattr(server, 'tunnel_password', tunnel_password)
|
||||||
|
keyring.delete_password(
|
||||||
|
KEY_RING_SERVICE_NAME, tunnel_name)
|
||||||
|
else:
|
||||||
|
setattr(server, 'tunnel_password', None)
|
||||||
|
passwords_migrated = True
|
||||||
|
except Exception as e:
|
||||||
|
error = 'Failed to migrate passwords stored using OS' \
|
||||||
|
' password manager.Error: {0}'.format(e)
|
||||||
|
current_app.logger.warning(error)
|
||||||
|
return passwords_migrated, error
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_passwords_from_pgadmin_db(servers, old_key, enc_key):
|
||||||
|
"""
|
||||||
|
Migrates passwords stored in pgadmin db
|
||||||
|
:param servers: list of servers
|
||||||
|
:param old_key: old encryption key
|
||||||
|
:param enc_key: new encryption key
|
||||||
|
:return: True if successful else False
|
||||||
|
"""
|
||||||
|
error = ''
|
||||||
|
passwords_migrated = False
|
||||||
|
try:
|
||||||
|
for ser in servers:
|
||||||
|
if ser.password:
|
||||||
|
password = decrypt(ser.password, old_key).decode()
|
||||||
|
server_password = encrypt(password, enc_key)
|
||||||
|
setattr(ser, 'password', server_password)
|
||||||
|
|
||||||
|
if ser.tunnel_password:
|
||||||
|
password = decrypt(ser.tunnel_password, old_key).decode()
|
||||||
|
tunnel_password = encrypt(password, enc_key)
|
||||||
|
setattr(ser, 'tunnel_password', tunnel_password)
|
||||||
|
passwords_migrated = True
|
||||||
|
except Exception as e:
|
||||||
|
error = 'Failed to migrate passwords stored using master password or' \
|
||||||
|
' user password password manager. Error: {0}'.format(e)
|
||||||
|
current_app.logger.warning(error)
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
|
||||||
|
return passwords_migrated, error
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_saved_passwords(master_key, master_password):
|
||||||
|
"""
|
||||||
|
Function will migrate password stored in pgadmin db and os secret storage
|
||||||
|
with separate entry for each server(initial keyring implementation #5123).
|
||||||
|
Now all saved passwords will be stored in pgadmin db which are encrypted
|
||||||
|
using master_key which is stored in local os storage.
|
||||||
|
:param master_key: encryption key from local os storage
|
||||||
|
:param master_password: set by user if MASTER_PASSWORD_REQUIRED=True
|
||||||
|
:param old_crypt_key: enc_key with ith passwords were encrypted when
|
||||||
|
MASTER_PASSWORD_REQUIRED=False
|
||||||
|
:return: True if all passwords are migrated successfully.
|
||||||
|
"""
|
||||||
|
error = ''
|
||||||
|
old_key = None
|
||||||
|
passwords_migrated = False
|
||||||
|
if config.ALLOW_SAVE_PASSWORD or config.ALLOW_SAVE_TUNNEL_PASSWORD:
|
||||||
|
# Get servers with saved password
|
||||||
|
all_server = Server.query.all()
|
||||||
|
saved_password_servers = [ser for ser in all_server
|
||||||
|
if ser.save_password or ser.tunnel_password]
|
||||||
|
|
||||||
|
servers_with_pwd_in_os_secret = []
|
||||||
|
servers_with_pwd_in_pgadmin_db = []
|
||||||
|
for ser in saved_password_servers:
|
||||||
|
if ser.password is None:
|
||||||
|
servers_with_pwd_in_os_secret.append(ser)
|
||||||
|
else:
|
||||||
|
servers_with_pwd_in_pgadmin_db.append(ser)
|
||||||
|
|
||||||
|
# No server passwords are saved
|
||||||
|
if len(saved_password_servers) == 0:
|
||||||
|
current_app.logger.warning(
|
||||||
|
'There are no saved passwords')
|
||||||
|
return passwords_migrated, error
|
||||||
|
|
||||||
|
# If not master password received return and follow
|
||||||
|
# normal Master password path
|
||||||
|
if config.MASTER_PASSWORD_REQUIRED:
|
||||||
|
if current_user.masterpass_check is not None and \
|
||||||
|
not master_password:
|
||||||
|
error = 'Master password required'
|
||||||
|
return passwords_migrated, error
|
||||||
|
elif master_password:
|
||||||
|
old_key = master_password
|
||||||
|
else:
|
||||||
|
old_key = current_user.password
|
||||||
|
|
||||||
|
# servers passwords stored with os storage are present.
|
||||||
|
if len(servers_with_pwd_in_os_secret) > 0:
|
||||||
|
current_app.logger.warning(
|
||||||
|
'Re-encrypting passwords saved using os password manager')
|
||||||
|
passwords_migrated, error = \
|
||||||
|
migrate_passwords_from_os_secret_storage(
|
||||||
|
servers_with_pwd_in_os_secret, master_key)
|
||||||
|
|
||||||
|
if len(servers_with_pwd_in_pgadmin_db) > 0 and old_key:
|
||||||
|
# if master_password present and masterpass_check is present,
|
||||||
|
# server passwords are encrypted with master password
|
||||||
|
current_app.logger.warning(
|
||||||
|
'Re-encrypting passwords saved using master password')
|
||||||
|
passwords_migrated, error = migrate_passwords_from_pgadmin_db(
|
||||||
|
servers_with_pwd_in_pgadmin_db, old_key, master_key)
|
||||||
|
# clear master_pass check once passwords are migrated
|
||||||
|
if passwords_migrated:
|
||||||
|
set_masterpass_check_text('', clear=True)
|
||||||
|
|
||||||
|
if passwords_migrated:
|
||||||
|
# commit the changes once all are migrated
|
||||||
|
db.session.commit()
|
||||||
|
# Delete passwords from os password manager
|
||||||
|
if len(servers_with_pwd_in_os_secret) > 0:
|
||||||
|
delete_saved_passwords_from_os_secret_storage(
|
||||||
|
servers_with_pwd_in_os_secret)
|
||||||
|
# Update driver manager with new passwords
|
||||||
|
update_session_manager(saved_password_servers)
|
||||||
|
current_app.logger.warning('Password migration is successful')
|
||||||
|
|
||||||
|
return passwords_migrated, error
|
||||||
|
|
||||||
|
|
||||||
def reencrpyt_server_passwords(user_id, old_key, new_key):
|
def reencrpyt_server_passwords(user_id, old_key, new_key):
|
||||||
"""
|
"""
|
||||||
This function will decrypt the saved passwords in SQLite with old key
|
This function will decrypt the saved passwords in SQLite with old key
|
||||||
@@ -242,7 +401,6 @@ def reencrpyt_server_passwords(user_id, old_key, new_key):
|
|||||||
|
|
||||||
for server in Server.query.filter_by(user_id=user_id).all():
|
for server in Server.query.filter_by(user_id=user_id).all():
|
||||||
manager = driver.connection_manager(server.id)
|
manager = driver.connection_manager(server.id)
|
||||||
|
|
||||||
_password_check(server, manager, old_key, new_key)
|
_password_check(server, manager, old_key, new_key)
|
||||||
|
|
||||||
if server.tunnel_password is not None:
|
if server.tunnel_password is not None:
|
||||||
@@ -274,13 +432,88 @@ def remove_saved_passwords(user_id):
|
|||||||
try:
|
try:
|
||||||
db.session.query(Server) \
|
db.session.query(Server) \
|
||||||
.filter(Server.user_id == user_id) \
|
.filter(Server.user_id == user_id) \
|
||||||
.update({Server.password: None, Server.tunnel_password: None})
|
.update({Server.password: None, Server.tunnel_password: None,
|
||||||
|
Server.save_password: 0})
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def delete_saved_passwords_from_os_secret_storage(servers):
|
||||||
|
"""
|
||||||
|
Delete passwords from os secret storage
|
||||||
|
:param servers: server list
|
||||||
|
:return: True if successful else False
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Clears entry created by initial keyring implementation
|
||||||
|
desktop_user_pass = \
|
||||||
|
KEY_RING_DESKTOP_USER.format(current_user.username)
|
||||||
|
if keyring.get_password(KEY_RING_SERVICE_NAME,desktop_user_pass):
|
||||||
|
keyring.delete_password(KEY_RING_SERVICE_NAME, desktop_user_pass)
|
||||||
|
|
||||||
|
if len(servers) > 0:
|
||||||
|
for server in servers:
|
||||||
|
server_name = KEY_RING_USERNAME_FORMAT.format(server.name,
|
||||||
|
server.id)
|
||||||
|
server_password = keyring.get_password(
|
||||||
|
KEY_RING_SERVICE_NAME, server_name)
|
||||||
|
if server_password:
|
||||||
|
keyring.delete_password(
|
||||||
|
KEY_RING_SERVICE_NAME, server_name)
|
||||||
|
else:
|
||||||
|
setattr(server, 'save_password', 0)
|
||||||
|
|
||||||
|
tunnel_name = KEY_RING_TUNNEL_FORMAT.format(server.name,
|
||||||
|
server.id)
|
||||||
|
tunnel_password = keyring.get_password(
|
||||||
|
KEY_RING_SERVICE_NAME, tunnel_name)
|
||||||
|
if tunnel_password:
|
||||||
|
keyring.delete_password(
|
||||||
|
KEY_RING_SERVICE_NAME, tunnel_name)
|
||||||
|
else:
|
||||||
|
setattr(server, 'tunnel_password', None)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# This means no server password to migrate
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning(
|
||||||
|
'Failed to delete passwords stored in OS password manager.'
|
||||||
|
'Error: {0}'.format(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def update_session_manager(user_id=None, servers=None):
|
||||||
|
"""
|
||||||
|
Updates the passwords in the session
|
||||||
|
:param user_id:
|
||||||
|
:param servers:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
from pgadmin.model import Server
|
||||||
|
from pgadmin.utils.driver import get_driver
|
||||||
|
driver = get_driver(config.PG_DEFAULT_DRIVER)
|
||||||
|
try:
|
||||||
|
if user_id:
|
||||||
|
for server in Server.query.\
|
||||||
|
filter_by(user_id=current_user.id).all():
|
||||||
|
manager = driver.connection_manager(server.id)
|
||||||
|
manager.update(server)
|
||||||
|
elif servers:
|
||||||
|
for server in servers:
|
||||||
|
manager = driver.connection_manager(server.id)
|
||||||
|
manager.update(server)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
db.session.commit()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def get_replication_type(conn, sversion):
|
def get_replication_type(conn, sversion):
|
||||||
status, res = conn.execute_dict(render_template(
|
status, res = conn.execute_dict(render_template(
|
||||||
"/servers/sql/#{0}#/replication_type.sql".format(sversion)
|
"/servers/sql/#{0}#/replication_type.sql".format(sversion)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class MasterPasswordTestCase(BaseTestGenerator):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
config.MASTER_PASSWORD_REQUIRED = True
|
config.MASTER_PASSWORD_REQUIRED = True
|
||||||
config.AUTHENTICATION_SOURCES = [INTERNAL]
|
config.AUTHENTICATION_SOURCES = [INTERNAL]
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
"""This function will check change password functionality."""
|
"""This function will check change password functionality."""
|
||||||
|
|||||||
@@ -116,15 +116,15 @@ def evaluate_and_patch_config(config: dict) -> dict:
|
|||||||
config['ENABLE_PSQL'] = True
|
config['ENABLE_PSQL'] = True
|
||||||
|
|
||||||
if config.get('SERVER_MODE'):
|
if config.get('SERVER_MODE'):
|
||||||
config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', True)
|
config.setdefault('USE_OS_SECRET_STORAGE', False)
|
||||||
config.setdefault('KEYRING_NAME', '')
|
config.setdefault('KEYRING_NAME', '')
|
||||||
else:
|
else:
|
||||||
k_name = keyring.get_keyring().name
|
k_name = keyring.get_keyring().name
|
||||||
|
# Setup USE_OS_SECRET_STORAGE false as no keyring backend available
|
||||||
if k_name == 'fail Keyring':
|
if k_name == 'fail Keyring':
|
||||||
config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', True)
|
config['USE_OS_SECRET_STORAGE'] = False
|
||||||
config.setdefault('KEYRING_NAME', '')
|
config['KEYRING_NAME'] = ''
|
||||||
else:
|
else:
|
||||||
config.setdefault('DISABLED_LOCAL_PASSWORD_STORAGE', False)
|
|
||||||
config.setdefault('KEYRING_NAME', k_name)
|
config.setdefault('KEYRING_NAME', k_name)
|
||||||
|
|
||||||
config.setdefault('SESSION_COOKIE_PATH', config.get('COOKIE_DEFAULT_PATH'))
|
config.setdefault('SESSION_COOKIE_PATH', config.get('COOKIE_DEFAULT_PATH'))
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export default function MasterPasswordContent({ closeModal, onResetPassowrd, onO
|
|||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span style={{ fontWeight: 'bold' }}>
|
<span style={{ fontWeight: 'bold' }}>
|
||||||
<FormNote text={gettext(`pgAdmin now stores any saved passwords in ${keyringName}. Enter the master password for your existing pgAdmin saved passwords and they will be migrated to the operating system store when you click OK.`)}></FormNote>
|
<FormNote text={gettext(`Saved passwords are encrypted using encryption key stored in ${keyringName}. Enter the master password for your existing pgAdmin saved passwords and they will be re-encrypted and saved when you click OK.`)}></FormNote>
|
||||||
</span>
|
</span>
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginTop='12px'>
|
<Box marginTop='12px'>
|
||||||
|
|||||||
@@ -81,39 +81,20 @@ export function checkMasterPassword(data, masterpass_callback_queue, cancel_call
|
|||||||
const api = getApiInstance();
|
const api = getApiInstance();
|
||||||
api.post(url_for('browser.set_master_password'), data).then((res)=> {
|
api.post(url_for('browser.set_master_password'), data).then((res)=> {
|
||||||
let isKeyring = res.data.data.keyring_name.length > 0;
|
let isKeyring = res.data.data.keyring_name.length > 0;
|
||||||
|
let error = res.data.data.errmsg;
|
||||||
|
|
||||||
if(!res.data.data.present) {
|
if(!res.data.data.present) {
|
||||||
if (res.data.data.invalid_master_password_hook){
|
showMasterPassword(res.data.data.reset, res.data.data.errmsg, masterpass_callback_queue, cancel_callback, res.data.data.keyring_name, res.data.data.master_password_hook);
|
||||||
if(res.data.data.is_error){
|
|
||||||
pgAdmin.Browser.notifier.error(res.data.data.errmsg);
|
|
||||||
}else{
|
|
||||||
pgAdmin.Browser.notifier.confirm(gettext('Reset Master Password'),
|
|
||||||
gettext('The master password retrieved from the master password hook utility is different from what was previously retrieved.') + '<br>'
|
|
||||||
+ gettext('Do you want to reset your master password to match?') + '<br><br>'
|
|
||||||
+ gettext('Note that this will close all open database connections and remove all saved passwords.'),
|
|
||||||
function() {
|
|
||||||
let _url = url_for('browser.reset_master_password');
|
|
||||||
api.delete(_url)
|
|
||||||
.then(() => {
|
|
||||||
pgAdmin.Browser.notifier.info('The master password has been reset.');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
pgAdmin.Browser.notifier.error(err.message);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
function() {/* If user clicks No */ return true;}
|
|
||||||
);}
|
|
||||||
}else{
|
|
||||||
showMasterPassword(res.data.data.reset, res.data.data.errmsg, masterpass_callback_queue, cancel_callback, res.data.data.keyring_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
masterPassCallbacks(masterpass_callback_queue);
|
masterPassCallbacks(masterpass_callback_queue);
|
||||||
|
|
||||||
if(isKeyring) {
|
if(isKeyring) {
|
||||||
pgAdmin.Browser.notifier.alert(gettext('Migration successful'),
|
if(error){
|
||||||
gettext(`Passwords previously saved by pgAdmin have been successfully migrated to ${res.data.data.keyring_name} and removed from the pgAdmin store.`));
|
pgAdmin.Browser.notifier.alert(gettext('Migration failed'),
|
||||||
|
gettext(`Passwords previously saved can not be re-encrypted using encryption key stored in the ${res.data.data.keyring_name}. due to ${error}`));
|
||||||
|
}else{
|
||||||
|
pgAdmin.Browser.notifier.alert(gettext('Migration successful'),
|
||||||
|
gettext(`Passwords previously saved are re-encrypted using encryption key stored in the ${res.data.data.keyring_name}.`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
@@ -122,7 +103,7 @@ export function checkMasterPassword(data, masterpass_callback_queue, cancel_call
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This functions is used to show the master password dialog.
|
// This functions is used to show the master password dialog.
|
||||||
export function showMasterPassword(isPWDPresent, errmsg, masterpass_callback_queue, cancel_callback, keyring_name='') {
|
export function showMasterPassword(isPWDPresent, errmsg, masterpass_callback_queue, cancel_callback, keyring_name='', master_password_hook='') {
|
||||||
const api = getApiInstance();
|
const api = getApiInstance();
|
||||||
let title = gettext('Set Master Password');
|
let title = gettext('Set Master Password');
|
||||||
if (keyring_name.length > 0)
|
if (keyring_name.length > 0)
|
||||||
@@ -130,47 +111,73 @@ export function showMasterPassword(isPWDPresent, errmsg, masterpass_callback_que
|
|||||||
else if (isPWDPresent)
|
else if (isPWDPresent)
|
||||||
title = gettext('Unlock Saved Passwords');
|
title = gettext('Unlock Saved Passwords');
|
||||||
|
|
||||||
pgAdmin.Browser.notifier.showModal(title, (onClose)=> {
|
if(master_password_hook){
|
||||||
return (
|
if(errmsg){
|
||||||
<MasterPasswordContent
|
pgAdmin.Browser.notifier.error(errmsg);
|
||||||
isPWDPresent= {isPWDPresent}
|
return true;
|
||||||
data={{'errmsg': errmsg}}
|
}else{
|
||||||
keyringName={keyring_name}
|
pgAdmin.Browser.notifier.confirm(gettext('Reset Master Password'),
|
||||||
closeModal={() => {
|
gettext('The master password retrieved from the master password hook utility is different from what was previously retrieved.') + '<br>'
|
||||||
onClose();
|
+ gettext('Do you want to reset your master password to match?') + '<br><br>'
|
||||||
}}
|
+ gettext('Note that this will close all open database connections and remove all saved passwords.'),
|
||||||
onResetPassowrd={(isKeyRing=false)=>{
|
function() {
|
||||||
pgAdmin.Browser.notifier.confirm(gettext('Reset Master Password'),
|
let _url = url_for('browser.reset_master_password');
|
||||||
gettext('This will remove all the saved passwords. This will also remove established connections to '
|
const api = getApiInstance();
|
||||||
+ 'the server and you may need to reconnect again. Do you wish to continue?'),
|
api.delete(_url)
|
||||||
function() {
|
.then(() => {
|
||||||
let _url = url_for('browser.reset_master_password');
|
pgAdmin.Browser.notifier.info('The master password has been reset.');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
pgAdmin.Browser.notifier.error(err.message);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
function() {/* If user clicks No */ return true;}
|
||||||
|
);}
|
||||||
|
}else{
|
||||||
|
|
||||||
api.delete(_url)
|
pgAdmin.Browser.notifier.showModal(title, (onClose)=> {
|
||||||
.then(() => {
|
return (
|
||||||
onClose();
|
<MasterPasswordContent
|
||||||
if(!isKeyRing) {
|
isPWDPresent= {isPWDPresent}
|
||||||
showMasterPassword(false, null, masterpass_callback_queue, cancel_callback);
|
data={{'errmsg': errmsg}}
|
||||||
}
|
keyringName={keyring_name}
|
||||||
})
|
closeModal={() => {
|
||||||
.catch((err) => {
|
onClose();
|
||||||
pgAdmin.Browser.notifier.error(err.message);
|
}}
|
||||||
});
|
onResetPassowrd={(isKeyRing=false)=>{
|
||||||
return true;
|
pgAdmin.Browser.notifier.confirm(gettext('Reset Master Password'),
|
||||||
},
|
gettext('This will remove all the saved passwords. This will also remove established connections to '
|
||||||
function() {/* If user clicks No */ return true;}
|
+ 'the server and you may need to reconnect again. Do you wish to continue?'),
|
||||||
);
|
function() {
|
||||||
}}
|
let _url = url_for('browser.reset_master_password');
|
||||||
onCancel={()=>{
|
|
||||||
cancel_callback?.();
|
api.delete(_url)
|
||||||
}}
|
.then(() => {
|
||||||
onOK={(formData) => {
|
onClose();
|
||||||
onClose();
|
if(!isKeyRing) {
|
||||||
checkMasterPassword(formData, masterpass_callback_queue, cancel_callback);
|
showMasterPassword(false, null, masterpass_callback_queue, cancel_callback);
|
||||||
}}
|
}
|
||||||
/>
|
})
|
||||||
);
|
.catch((err) => {
|
||||||
});
|
pgAdmin.Browser.notifier.error(err.message);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
function() {/* If user clicks No */ return true;}
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onCancel={()=>{
|
||||||
|
cancel_callback?.();
|
||||||
|
}}
|
||||||
|
onOK={(formData) => {
|
||||||
|
onClose();
|
||||||
|
checkMasterPassword(formData, masterpass_callback_queue, cancel_callback);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showChangeServerPassword() {
|
export function showChangeServerPassword() {
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ ACCESS_DENIED_MESSAGE = gettext(
|
|||||||
|
|
||||||
|
|
||||||
KEY_RING_SERVICE_NAME = 'pgAdmin4'
|
KEY_RING_SERVICE_NAME = 'pgAdmin4'
|
||||||
|
KEY_RING_USER_NAME = 'pgadmin4-master-password'
|
||||||
KEY_RING_USERNAME_FORMAT = KEY_RING_SERVICE_NAME + '-{0}-{1}'
|
KEY_RING_USERNAME_FORMAT = KEY_RING_SERVICE_NAME + '-{0}-{1}'
|
||||||
KEY_RING_TUNNEL_FORMAT = KEY_RING_SERVICE_NAME + '-tunnel-{0}-{1}'
|
KEY_RING_TUNNEL_FORMAT = KEY_RING_SERVICE_NAME + '-tunnel-{0}-{1}'
|
||||||
KEY_RING_DESKTOP_USER = KEY_RING_SERVICE_NAME + '-desktop-user-{0}'
|
KEY_RING_DESKTOP_USER = KEY_RING_SERVICE_NAME + '-desktop-user-{0}'
|
||||||
|
|||||||
@@ -268,18 +268,11 @@ class Connection(BaseConnection):
|
|||||||
|
|
||||||
manager = self.manager
|
manager = self.manager
|
||||||
|
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
crypt_key_present, crypt_key = get_crypt_key()
|
||||||
crypt_key_present, crypt_key = get_crypt_key()
|
if not crypt_key_present:
|
||||||
|
raise CryptKeyMissing()
|
||||||
if not crypt_key_present:
|
password, encpass, is_update_password = \
|
||||||
raise CryptKeyMissing()
|
self._check_user_password(kwargs)
|
||||||
|
|
||||||
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
|
passfile = kwargs['passfile'] if 'passfile' in kwargs else None
|
||||||
tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \
|
tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \
|
||||||
@@ -305,15 +298,13 @@ class Connection(BaseConnection):
|
|||||||
if self.reconnecting is not False:
|
if self.reconnecting is not False:
|
||||||
self.password = None
|
self.password = None
|
||||||
|
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
if not crypt_key_present:
|
||||||
is_error, errmsg, password = self._decode_password(encpass,
|
raise CryptKeyMissing()
|
||||||
manager,
|
|
||||||
password,
|
is_error, errmsg, password = self._decode_password(
|
||||||
crypt_key)
|
encpass, manager, password, crypt_key)
|
||||||
if is_error:
|
if is_error:
|
||||||
return False, errmsg
|
return False, errmsg
|
||||||
else:
|
|
||||||
password = encpass
|
|
||||||
|
|
||||||
# If no password credential is found then connect request might
|
# If no password credential is found then connect request might
|
||||||
# come from Query tool, ViewData grid, debugger etc tools.
|
# come from Query tool, ViewData grid, debugger etc tools.
|
||||||
@@ -1580,13 +1571,11 @@ Failed to reset the connection to the server due to following error:
|
|||||||
user = User.query.filter_by(id=current_user.id).first()
|
user = User.query.filter_by(id=current_user.id).first()
|
||||||
if user is None:
|
if user is None:
|
||||||
return False, self.UNAUTHORIZED_REQUEST
|
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
|
|
||||||
|
|
||||||
password = decrypt(password, crypt_key)\
|
crypt_key_present, crypt_key = get_crypt_key()
|
||||||
.decode()
|
if not crypt_key_present:
|
||||||
|
return False, crypt_key
|
||||||
|
password = decrypt(password, crypt_key).decode()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with ConnectionLocker(self.manager.kerberos_conn):
|
with ConnectionLocker(self.manager.kerberos_conn):
|
||||||
|
|||||||
@@ -242,8 +242,7 @@ WHERE db.oid = {0}""".format(did))
|
|||||||
"Could not find the specified database."
|
"Could not find the specified database."
|
||||||
))
|
))
|
||||||
|
|
||||||
if not get_crypt_key()[0] and (
|
if not get_crypt_key()[0]:
|
||||||
config.SERVER_MODE or config.DISABLED_LOCAL_PASSWORD_STORAGE):
|
|
||||||
# the reason its not connected might be missing key
|
# the reason its not connected might be missing key
|
||||||
raise CryptKeyMissing()
|
raise CryptKeyMissing()
|
||||||
|
|
||||||
@@ -537,16 +536,10 @@ WHERE db.oid = {0}""".format(did))
|
|||||||
|
|
||||||
def export_password_env(self, env):
|
def export_password_env(self, env):
|
||||||
if self.password:
|
if self.password:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
crypt_key_present, crypt_key = get_crypt_key()
|
||||||
crypt_key_present, crypt_key = get_crypt_key()
|
if not crypt_key_present:
|
||||||
if not crypt_key_present:
|
return False, crypt_key
|
||||||
return False, crypt_key
|
password = decrypt(self.password, crypt_key).decode()
|
||||||
password = decrypt(self.password, crypt_key).decode()
|
|
||||||
elif hasattr(self.password, 'decode'):
|
|
||||||
password = self.password.decode('utf-8')
|
|
||||||
else:
|
|
||||||
password = self.password
|
|
||||||
|
|
||||||
os.environ[str(env)] = password
|
os.environ[str(env)] = password
|
||||||
elif self.passexec:
|
elif self.passexec:
|
||||||
password = self.passexec.get()
|
password = self.passexec.get()
|
||||||
@@ -565,18 +558,15 @@ WHERE db.oid = {0}""".format(did))
|
|||||||
return False, gettext("Unauthorized request.")
|
return False, gettext("Unauthorized request.")
|
||||||
|
|
||||||
if tunnel_password is not None and tunnel_password != '':
|
if tunnel_password is not None and tunnel_password != '':
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
crypt_key_present, crypt_key = get_crypt_key()
|
||||||
crypt_key_present, crypt_key = get_crypt_key()
|
if not crypt_key_present:
|
||||||
if not crypt_key_present:
|
raise CryptKeyMissing()
|
||||||
raise CryptKeyMissing()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if config.DISABLED_LOCAL_PASSWORD_STORAGE:
|
tunnel_password = decrypt(tunnel_password, crypt_key)
|
||||||
tunnel_password = decrypt(tunnel_password, crypt_key)
|
# password is in bytes, for python3 we need it in string
|
||||||
# password is in bytes, for python3 we need it in string
|
if isinstance(tunnel_password, bytes):
|
||||||
if isinstance(tunnel_password, bytes):
|
tunnel_password = tunnel_password.decode()
|
||||||
tunnel_password = tunnel_password.decode()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.exception(e)
|
current_app.logger.exception(e)
|
||||||
return False, gettext("Failed to decrypt the SSH tunnel "
|
return False, gettext("Failed to decrypt the SSH tunnel "
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
|
import secrets
|
||||||
|
|
||||||
|
import keyring
|
||||||
|
from keyring.errors import KeyringError, KeyringLocked, NoKeyringError
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from flask import current_app, session, current_app
|
from flask import current_app
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from pgadmin.model import db, User, Server
|
from pgadmin.model import db, User, Server
|
||||||
|
from pgadmin.utils.constants import KEY_RING_SERVICE_NAME, KEY_RING_USER_NAME
|
||||||
from pgadmin.utils.crypto import encrypt, decrypt
|
from pgadmin.utils.crypto import encrypt, decrypt
|
||||||
|
|
||||||
|
|
||||||
MASTERPASS_CHECK_TEXT = 'ideas are bulletproof'
|
MASTERPASS_CHECK_TEXT = 'ideas are bulletproof'
|
||||||
|
|
||||||
|
|
||||||
@@ -23,29 +28,41 @@ def get_crypt_key():
|
|||||||
:return: the key
|
:return: the key
|
||||||
"""
|
"""
|
||||||
enc_key = current_app.keyManager.get()
|
enc_key = current_app.keyManager.get()
|
||||||
|
|
||||||
# if desktop mode and master pass disabled then use the password hash
|
# if desktop mode and master pass disabled then use the password hash
|
||||||
if not config.MASTER_PASSWORD_REQUIRED \
|
if not config.MASTER_PASSWORD_REQUIRED and\
|
||||||
and not config.SERVER_MODE:
|
not config.USE_OS_SECRET_STORAGE and not config.SERVER_MODE:
|
||||||
return True, current_user.password
|
return True, current_user.password
|
||||||
# if desktop mode and master pass enabled
|
# if desktop mode and master pass enabled
|
||||||
elif config.MASTER_PASSWORD_REQUIRED and \
|
elif config.MASTER_PASSWORD_REQUIRED and \
|
||||||
config.MASTER_PASSWORD_HOOK is None\
|
enc_key is None:
|
||||||
and enc_key is None:
|
|
||||||
return False, None
|
return False, None
|
||||||
elif not config.MASTER_PASSWORD_REQUIRED and config.SERVER_MODE and \
|
|
||||||
'pass_enc_key' in session:
|
|
||||||
return True, session['pass_enc_key']
|
|
||||||
elif config.MASTER_PASSWORD_REQUIRED and config.SERVER_MODE and \
|
|
||||||
config.MASTER_PASSWORD_HOOK and current_user.password is None:
|
|
||||||
cmd = config.MASTER_PASSWORD_HOOK
|
|
||||||
command = cmd.replace('%u', current_user.username) \
|
|
||||||
if '%u' in cmd else cmd
|
|
||||||
return get_master_password_from_master_hook(command)
|
|
||||||
else:
|
else:
|
||||||
return True, enc_key
|
return True, enc_key
|
||||||
|
|
||||||
|
|
||||||
|
def get_master_password_key_from_os_secret():
|
||||||
|
master_key = None
|
||||||
|
try:
|
||||||
|
# Try to get master key is from local os storage
|
||||||
|
master_key = keyring.get_password(
|
||||||
|
KEY_RING_SERVICE_NAME, KEY_RING_USER_NAME)
|
||||||
|
except KeyringLocked as e:
|
||||||
|
current_app.logger.warning(
|
||||||
|
'Failed to retrieve master key because Access Denied.'
|
||||||
|
' Error: {0}'.format(e))
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning(
|
||||||
|
'Failed to set encryption key using OS password manager'
|
||||||
|
', fallback to master password. Error: {0}'.format(e))
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
return master_key
|
||||||
|
|
||||||
|
|
||||||
|
def generate_master_password_key_for_os_secret():
|
||||||
|
return secrets.token_urlsafe(12)
|
||||||
|
|
||||||
|
|
||||||
def validate_master_password(password):
|
def validate_master_password(password):
|
||||||
"""
|
"""
|
||||||
Validate the password/key against the stored encrypted text
|
Validate the password/key against the stored encrypted text
|
||||||
@@ -114,6 +131,47 @@ def cleanup_master_password():
|
|||||||
manager.update(server)
|
manager.update(server)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_local_storage_master_key():
|
||||||
|
"""
|
||||||
|
Deletes the auto generated master key stored in keyring
|
||||||
|
"""
|
||||||
|
if not config.SERVER_MODE and not config.USE_OS_SECRET_STORAGE:
|
||||||
|
# Retrieve from os secret storage
|
||||||
|
try:
|
||||||
|
# try to get key
|
||||||
|
master_key = keyring.get_password(KEY_RING_SERVICE_NAME,
|
||||||
|
KEY_RING_USER_NAME)
|
||||||
|
if master_key:
|
||||||
|
keyring.delete_password(KEY_RING_SERVICE_NAME,
|
||||||
|
KEY_RING_USER_NAME)
|
||||||
|
from pgadmin.browser.server_groups.servers.utils \
|
||||||
|
import remove_saved_passwords
|
||||||
|
remove_saved_passwords(current_user.id)
|
||||||
|
|
||||||
|
from pgadmin.utils.driver import get_driver
|
||||||
|
driver = get_driver(config.PG_DEFAULT_DRIVER)
|
||||||
|
for server in Server.query.filter_by(
|
||||||
|
user_id=current_user.id).all():
|
||||||
|
manager = driver.connection_manager(server.id)
|
||||||
|
manager.update(server)
|
||||||
|
current_app.logger.warning(
|
||||||
|
'Deleted master key stored in OS password manager.')
|
||||||
|
except NoKeyringError as e:
|
||||||
|
current_app.logger.warning(
|
||||||
|
' Failed to delete master key stored in OS password manager'
|
||||||
|
' because Keyring backend not found. Error: {0}'.format(e))
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
except KeyringLocked as e:
|
||||||
|
current_app.logger.warning(
|
||||||
|
' Failed to delete master key stored in OS password manager'
|
||||||
|
' because of Access Denied. Error: {0}'.format(e))
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning(
|
||||||
|
'Failed to delete master key stored in OS password manager.')
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
|
||||||
|
|
||||||
def process_masterpass_disabled():
|
def process_masterpass_disabled():
|
||||||
"""
|
"""
|
||||||
On master password disable, remove the connection data from session as it
|
On master password disable, remove the connection data from session as it
|
||||||
@@ -129,28 +187,30 @@ def process_masterpass_disabled():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_master_password_from_master_hook(command):
|
def get_master_password_from_master_hook():
|
||||||
"""
|
"""
|
||||||
This method executes specified command & returns output.
|
This method executes specified command & returns output.
|
||||||
:param command: Shell command with absolute path
|
:param command: Shell command with absolute path
|
||||||
:return: Output of command.
|
:return: Output of command.
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
cmd = config.MASTER_PASSWORD_HOOK
|
||||||
|
command = cmd.replace('%u', current_user.username) \
|
||||||
|
if '%u' in cmd else cmd
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
|
p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
|
||||||
out, err = p.communicate()
|
out, err = p.communicate()
|
||||||
if p.returncode == 0:
|
if p.returncode == 0:
|
||||||
output = out.decode() if hasattr(out, 'decode') else out
|
output = out.decode() if hasattr(out, 'decode') else out
|
||||||
output = output.strip()
|
output = output.strip()
|
||||||
return True, output
|
return output
|
||||||
else:
|
else:
|
||||||
error = "Command '{0}' failed, exit-code={1} error = {2}".format(
|
error = "Command '{0}' failed, exit-code={1} error = {2}".format(
|
||||||
command, p.returncode, str(err))
|
command, p.returncode, str(err))
|
||||||
current_app.logger.error(error)
|
current_app.logger.error(error)
|
||||||
return False, None
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.exception(
|
current_app.logger.exception(
|
||||||
'Failed to retrieve master password from the master password hook'
|
'Failed to retrieve master password from the master password hook'
|
||||||
' utility.Error: {0}'.format(e)
|
' utility.Error: {0}'.format(e)
|
||||||
)
|
)
|
||||||
return False, None
|
return None
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ if config.SERVER_MODE is True:
|
|||||||
|
|
||||||
# disable master password for test cases
|
# disable master password for test cases
|
||||||
config.MASTER_PASSWORD_REQUIRED = False
|
config.MASTER_PASSWORD_REQUIRED = False
|
||||||
|
config.USE_OS_SECRET_STORAGE = False
|
||||||
|
|
||||||
from regression import test_setup
|
from regression import test_setup
|
||||||
from regression.feature_utils.app_starter import AppStarter
|
from regression.feature_utils.app_starter import AppStarter
|
||||||
|
|||||||
Reference in New Issue
Block a user