Hi,
PFA patch to fix the issue where changing a database server password
functionlity was not working if user has made connection using pgpass file.
Also ViewData/Query tool functionality was not working with pgpass file.
RM#2720
--
Regards,
Murtuza Zabuawala
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py
b/web/pgadmin/browser/server_groups/servers/__init__.py
index 7cd7ef5..5330942 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -240,7 +240,8 @@ class ServerNode(PGChildNodeView):
'change_password': [{'post': 'change_password'}],
'wal_replay': [{
'delete': 'pause_wal_replay', 'put': 'resume_wal_replay'
- }]
+ }],
+ 'check_pgpass': [{'get': 'check_pgpass'}]
})
EXP_IP4 = "^\s*((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."\
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\."\
@@ -1118,12 +1119,43 @@ class ServerNode(PGChildNodeView):
"""
try:
data = json.loads(request.form['data'], encoding='utf-8')
- if data and ('password' not in data or
- data['password'] == '' or
- 'newPassword' not in data or
- data['newPassword'] == '' or
- 'confirmPassword' not in data or
- data['confirmPassword'] == ''):
+
+ # Fetch Server Details
+ server = Server.query.filter_by(id=sid).first()
+ if server is None:
+ return bad_request(gettext("Server not found."))
+
+ # Fetch User Details.
+ user = User.query.filter_by(id=current_user.id).first()
+ if user is None:
+ return unauthorized(gettext("Unauthorized request."))
+
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+ conn = manager.connection()
+ is_passfile = False
+
+ # If there is no password found for the server
+ # then check for pgpass file
+ if not server.password and not manager.password:
+ if server.passfile and manager.passfile and \
+ server.passfile == manager.passfile:
+ is_passfile = True
+
+ # Check for password only if there is no pgpass file used
+ if not is_passfile:
+ if data and ('password' not in data or data['password'] == ''):
+ return make_json_response(
+ status=400,
+ success=0,
+ errormsg=gettext(
+ "Could not find the required parameter(s)."
+ )
+ )
+
+ if data and ('newPassword' not in data or
+ data['newPassword'] == '' or
+ 'confirmPassword' not in data or
+ data['confirmPassword'] == ''):
return make_json_response(
status=400,
success=0,
@@ -1141,29 +1173,18 @@ class ServerNode(PGChildNodeView):
)
)
- # Fetch Server Details
- server = Server.query.filter_by(id=sid).first()
- if server is None:
- return bad_request(gettext("Server not found."))
-
- # Fetch User Details.
- user = User.query.filter_by(id=current_user.id).first()
- if user is None:
- return unauthorized(gettext("Unauthorized request."))
-
- manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
- conn = manager.connection()
+ # Check against old password only if no pgpass file
+ if not is_passfile:
+ decrypted_password = decrypt(manager.password, user.password)
- decrypted_password = decrypt(manager.password, user.password)
+ if isinstance(decrypted_password, bytes):
+ decrypted_password = decrypted_password.decode()
- if isinstance(decrypted_password, bytes):
- decrypted_password = decrypted_password.decode()
+ password = data['password']
- password = data['password']
-
- # Validate old password before setting new.
- if password != decrypted_password:
- return unauthorized(gettext("Incorrect password."))
+ # Validate old password before setting new.
+ if password != decrypted_password:
+ return unauthorized(gettext("Incorrect password."))
# Hash new password before saving it.
password = pqencryptpassword(data['newPassword'], manager.user)
@@ -1178,15 +1199,17 @@ class ServerNode(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
- password = encrypt(data['newPassword'], user.password)
- # Check if old password was stored in pgadmin4 sqlite database.
- # If yes then update that password.
- if server.password is not None and config.ALLOW_SAVE_PASSWORD:
- setattr(server, 'password', password)
- db.session.commit()
- # Also update password in connection manager.
- manager.password = password
- manager.update_session()
+ # Store password in sqlite only if no pgpass file
+ if not is_passfile:
+ password = encrypt(data['newPassword'], user.password)
+ # Check if old password was stored in pgadmin4 sqlite database.
+ # If yes then update that password.
+ if server.password is not None and config.ALLOW_SAVE_PASSWORD:
+ setattr(server, 'password', password)
+ db.session.commit()
+ # Also update password in connection manager.
+ manager.password = password
+ manager.update_session()
return make_json_response(
status=200,
@@ -1277,5 +1300,46 @@ class ServerNode(PGChildNodeView):
"""
return self.wal_replay(sid, True)
+ def check_pgpass(self, gid, sid):
+ """
+ This function is used to check whether server is connected
+ using pgpass file or not
+
+ Args:
+ gid: Group id
+ sid: Server id
+ """
+ is_pgpass = False
+ server = Server.query.filter_by(
+ user_id=current_user.id, id=sid
+ ).first()
+
+ if server is None:
+ return make_json_response(
+ success=0,
+ errormsg=gettext("Could not find the required server.")
+ )
+
+ try:
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
+ conn = manager.connection()
+ if not conn.connected():
+ return gone(
+ errormsg=_('Please connect the server.')
+ )
+
+ if not server.password or not manager.password:
+ if server.passfile and manager.passfile and \
+ server.passfile == manager.passfile:
+ is_pgpass = True
+ return make_json_response(
+ success=1,
+ data=dict({'is_pgpass': is_pgpass}),
+ )
+ except Exception as e:
+ current_app.logger.error(
+ 'Cannot able to fetch pgpass status'
+ )
+ return internal_server_error(errormsg=str(e))
ServerNode.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js
b/web/pgadmin/browser/server_groups/servers/static/js/server.js
index f0feba1..518bdf2 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -365,7 +365,9 @@ define('pgadmin.node.server', [
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined,
node = d && pgBrowser.Nodes[d._type],
- url = obj.generate_url(i, 'change_password', d, true);
+ url = obj.generate_url(i, 'change_password', d, true),
+ is_pgpass_file_used = false,
+ check_pgpass_url = obj.generate_url(i, 'check_pgpass', d, true);
if (!d)
return false;
@@ -387,8 +389,8 @@ define('pgadmin.node.server', [
type: 'text', disabled: true, control: 'input'
},{
name: 'password', label: gettext('Current Password'),
- type: 'password', disabled: false, control: 'input',
- required: true
+ type: 'password', disabled: function() { return
is_pgpass_file_used },
+ control: 'input', required: true
},{
name: 'newPassword', label: gettext('New Password'),
type: 'password', disabled: false, control: 'input',
@@ -410,9 +412,11 @@ define('pgadmin.node.server', [
setup:function() {
return {
buttons: [{
- text: gettext('Ok'), key: 13, className: 'btn
btn-primary', attrs:{name:'submit'}
- },{
- text: gettext('Cancel'), key: 27, className: 'btn
btn-danger', attrs:{name:'cancel'}
+ text: gettext('Ok'), key: 13, className: 'btn
btn-primary',
+ attrs:{name:'submit'}
+ },{
+ text: gettext('Cancel'), key: 27, className: 'btn
btn-danger',
+ attrs:{name:'cancel'}
}],
// Set options for dialog
options: {
@@ -436,15 +440,18 @@ define('pgadmin.node.server', [
},
prepare: function() {
var self = this;
- // Disable Backup button until user provides Filename
+ // Disable Ok button until user provides input
this.__internal.buttons[0].element.disabled = true;
- var $container = $("<div class='change_password'></div>"),
- newpasswordmodel = new newPasswordModel({'user_name':
self.user_name});
- var view = this.view = new Backform.Form({
- el: $container,
- model: newpasswordmodel,
- fields: passwordChangeFields});
+ var $container = $("<div class='change_password'></div>"),
+ newpasswordmodel = new newPasswordModel(
+ {'user_name': self.user_name}
+ ),
+ view = this.view = new Backform.Form({
+ el: $container,
+ model: newpasswordmodel,
+ fields: passwordChangeFields
+ });
view.render();
@@ -457,7 +464,9 @@ define('pgadmin.node.server', [
newPassword = this.get('newPassword'),
confirmPassword = this.get('confirmPassword');
- if (_.isUndefined(password) || _.isNull(password) ||
password == '' ||
+ // Only check password field if pgpass file is not
available
+ if ((!is_pgpass_file_used &&
+ (_.isUndefined(password) || _.isNull(password) ||
password == '')) ||
_.isUndefined(newPassword) || _.isNull(newPassword) ||
newPassword == '' ||
_.isUndefined(confirmPassword) ||
_.isNull(confirmPassword) || confirmPassword == '') {
self.__internal.buttons[0].element.disabled = true;
@@ -488,6 +497,16 @@ define('pgadmin.node.server', [
data:{'data': JSON.stringify(args) },
success: function(res) {
if (res.success) {
+ // Notify user to update pgpass file
+ if(is_pgpass_file_used) {
+ alertify.alert(
+ gettext("Change Password"),
+ gettext("Please make sure to disconnect the
server"
+ + " and update the new password in the pgpass
file"
+ + " before performing any other operation")
+ );
+ }
+
alertify.success(res.info);
self.close();
} else {
@@ -509,7 +528,26 @@ define('pgadmin.node.server', [
});
}
- alertify.changeServerPassword(d).resizeTo('40%','52%');
+ // Call to check if server is using pgpass file or not
+ $.ajax({
+ url: check_pgpass_url,
+ method:'GET',
+ success: function(res) {
+ if (res.success && res.data.is_pgpass) {
+ is_pgpass_file_used = true;
+ }
+ alertify.changeServerPassword(d).resizeTo('40%','52%');
+ },
+ error: function(xhr, status, error) {
+ try {
+ var err = $.parseJSON(xhr.responseText);
+ if (err.success == 0) {
+ alertify.error(err.errormsg);
+ }
+ } catch (e) {}
+ }
+ });
+
return false;
},
diff --git a/web/pgadmin/utils/__init__.py b/web/pgadmin/utils/__init__.py
index 701a38c..63ec6e7 100644
--- a/web/pgadmin/utils/__init__.py
+++ b/web/pgadmin/utils/__init__.py
@@ -10,7 +10,7 @@
from collections import defaultdict
from operator import attrgetter
-from flask import Blueprint
+from flask import Blueprint, current_app
from .paths import get_storage_directory
from .preferences import Preferences
@@ -184,7 +184,6 @@ def file_quote(_p):
return _p.encode(fs_encoding)
return _p
-
if IS_WIN:
import ctypes
from ctypes import wintypes
@@ -252,3 +251,32 @@ else:
def document_dir():
return os.path.realpath(os.path.expanduser(u'~/'))
+
+
+def get_complete_file_path(file):
+ """
+ Args:
+ file: File returned by file manager
+
+ Returns:
+ Full path for the file
+ """
+ if not file:
+ return None
+
+ # If desktop mode
+ if current_app.PGADMIN_RUNTIME or not current_app.config['SERVER_MODE']:
+ return file if os.path.isfile(file) else None
+
+ storage_dir = get_storage_directory()
+ if storage_dir:
+ file = os.path.join(
+ storage_dir,
+ file.lstrip(u'/').lstrip(u'\\')
+ )
+ if IS_WIN:
+ file = file.replace('\\', '/')
+ file = fs_short_path(file)
+
+ return file if os.path.isfile(file) else None
+
diff --git a/web/pgadmin/utils/driver/psycopg2/__init__.py
b/web/pgadmin/utils/driver/psycopg2/__init__.py
index ece1d12..bba033e 100644
--- a/web/pgadmin/utils/driver/psycopg2/__init__.py
+++ b/web/pgadmin/utils/driver/psycopg2/__init__.py
@@ -31,6 +31,7 @@ from psycopg2.extensions import adapt, encodings
import config
from pgadmin.model import Server, User
from pgadmin.utils.exception import ConnectionLost
+from pgadmin.utils import get_complete_file_path
from .keywords import ScanKeyword
from ..abstract import BaseDriver, BaseConnection
from .cursor import DictCursor
@@ -366,6 +367,13 @@ class Connection(BaseConnection):
str(e)
)
+ # If no password credential is found then connect request might
+ # come from Query tool, ViewData grid, debugger etc tools.
+ # we will check for pgpass file availability from connection manager
+ # if it's present then we will use it
+ if not password and not encpass and not passfile:
+ passfile = mgr.passfile if mgr.passfile else None
+
try:
if hasattr(str, 'decode'):
database = self.db.encode('utf-8')
@@ -387,12 +395,12 @@ class Connection(BaseConnection):
user=user,
password=password,
async=self.async,
- passfile=passfile,
+ passfile=get_complete_file_path(passfile),
sslmode=mgr.ssl_mode,
- sslcert=mgr.sslcert,
- sslkey=mgr.sslkey,
- sslrootcert=mgr.sslrootcert,
- sslcrl=mgr.sslcrl,
+ sslcert=get_complete_file_path(mgr.sslcert),
+ sslkey=get_complete_file_path(mgr.sslkey),
+ sslrootcert=get_complete_file_path(mgr.sslrootcert),
+ sslcrl=get_complete_file_path(mgr.sslcrl),
sslcompression=True if mgr.sslcompression else False
)
@@ -1256,12 +1264,12 @@ Failed to execute query (execute_void) for the server
#{server_id} - {conn_id}
database=self.db,
user=mgr.user,
password=password,
- passfile=mgr.passfile,
+ passfile=get_complete_file_path(mgr.passfile),
sslmode=mgr.ssl_mode,
- sslcert=mgr.sslcert,
- sslkey=mgr.sslkey,
- sslrootcert=mgr.sslrootcert,
- sslcrl=mgr.sslcrl,
+ sslcert=get_complete_file_path(mgr.sslcert),
+ sslkey=get_complete_file_path(mgr.sslkey),
+ sslrootcert=get_complete_file_path(mgr.sslrootcert),
+ sslcrl=get_complete_file_path(mgr.sslcrl),
sslcompression=True if mgr.sslcompression else False
)
@@ -1532,12 +1540,14 @@ Failed to reset the connection to the server due to
following error:
database=self.db,
user=self.manager.user,
password=password,
- passfile=self.manager.passfile,
+ passfile=get_complete_file_path(self.manager.passfile),
sslmode=self.manager.ssl_mode,
- sslcert=self.manager.sslcert,
- sslkey=self.manager.sslkey,
- sslrootcert=self.manager.sslrootcert,
- sslcrl=self.manager.sslcrl,
+ sslcert=get_complete_file_path(self.manager.sslcert),
+ sslkey=get_complete_file_path(self.manager.sslkey),
+ sslrootcert=get_complete_file_path(
+ self.manager.sslrootcert
+ ),
+ sslcrl=get_complete_file_path(self.manager.sslcrl),
sslcompression=True if self.manager.sslcompression
else False
)
@@ -1876,7 +1886,8 @@ WHERE db.oid = {0}""".format(did))
from pgadmin.browser.server_groups.servers.types import ServerType
self.pinged = datetime.datetime.now()
try:
- data['password'] = data['password'].encode('utf-8')
+ if 'password' in data and data['password']:
+ data['password'] = data['password'].encode('utf-8')
except:
pass