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
 

Reply via email to