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 = $("
"), - newpasswordmodel = new newPasswordModel({'user_name': self.user_name}); - var view = this.view = new Backform.Form({ - el: $container, - model: newpasswordmodel, - fields: passwordChangeFields}); + var $container = $("
"), + 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