Hi, PFA patch for changing server password.
-- *Harshal Dhumal* *Software Engineer * EenterpriseDB <http://www.enterprisedb.com>
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index b2fd313..fe1f1b6 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -19,7 +19,7 @@ from pgadmin.browser.utils import PGChildNodeView import traceback from flask.ext.babel import gettext import pgadmin.browser.server_groups as sg -from pgadmin.utils.crypto import encrypt +from pgadmin.utils.crypto import encrypt, decrypt, pqencryptpassword from config import PG_DEFAULT_DRIVER from pgadmin.browser.server_groups.servers.types import ServerType import config @@ -183,7 +183,9 @@ class ServerNode(PGChildNodeView): [{'get': 'reload_configuration'}], 'connect': [{ 'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect' - }] + }], + 'change_password': [{ + 'post': 'change_password'}] }) def nodes(self, gid): @@ -656,7 +658,7 @@ class ServerNode(PGChildNodeView): # Encrypt the password before saving with user's login password key. try: - password = encrypt(password, user.password) \ + password = encrypt(password, user.password) \ if password is not None else server.password except Exception as e: current_app.logger.exception(e) @@ -781,5 +783,92 @@ class ServerNode(PGChildNodeView): return make_json_response(data={'status': False, 'result': gettext('Not connected to the server or the connection to the server has been closed.')}) + def change_password(self, gid, sid): + try: + data = json.loads(request.form['data']) + 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'] == ''): + return make_json_response( + status=400, + success=0, + errormsg=gettext( + "Couldn't find the required parameter(s)." + ) + ) + + if data['newPassword'] != data['confirmPassword']: + return make_json_response( + status=200, + success=0, + errormsg=gettext( + "Passwords do not match." + ) + ) + + # 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.")) + + from pgadmin.utils.driver import get_driver + manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid) + conn = manager.connection() + + password = data['password'] + + decrypted_password = decrypt(manager.password, user.password) + + if isinstance(decrypted_password, bytes): + decrypted_password = decrypted_password.decode() + + # Validate old password before setting new. + if password != decrypted_password: + return unauthorized(gettext("Incorrect password.")) + + # Encrypt password before setting it. + password = pqencryptpassword(data['newPassword'], manager.user) + + SQL = render_template("/".join([ + 'servers/sql', + '9.2_plus' if manager.version >= 90200 else '9.1_plus', + 'alter_with_encrypted_password.sql' + ]), + conn=conn, _=gettext, + user=manager.user, encrypted_password=password) + + status, res = conn.execute_scalar(SQL) + + 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: + 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, + success=1, + info=gettext( + "Password changed successfully." + ) + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) ServerNode.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js index da52f74..0ff28a9 100644 --- a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js @@ -45,6 +45,12 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { applies: ['tools', 'context'], callback: 'reload_configuration', category: 'reload', priority: 6, label: '{{ _('Reload Configuration...') }}', icon: 'fa fa-repeat', enable : 'enable_reload_config' + }, + { + name: 'change_passowrd', node: 'server', module: this, + applies: ['file'], callback: 'change_passowrd', + priority: 7, label: '{{ _('Change Passowrd...') }}', + icon: 'fa fa-lock', enable : 'is_connected' }]); pgBrowser.messages['PRIV_GRANTEE_NOT_SPECIFIED'] = @@ -199,6 +205,159 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { }); return false; + }, + /* Change password */ + change_passowrd: function(args){ + var input = args || {}; + obj = this, + t = pgBrowser.tree, + 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); + + if (!d) + return false; + + if(!alertify.changeServerPassword) { + var newPasswordModel = Backbone.Model.extend({ + defaults: { + password: undefined, + newPassword: undefined, + confirmPassword: undefined + }, + validate: function() { + return null; + } + }), + passwordChangeFields = [{ + name: 'password', label: '{{ _('Current Password') }}', + type: 'password', disabled: false, control: 'input', + required: true + },{ + name: 'newPassword', label: '{{ _('New Password') }}', + type: 'password', disabled: false, control: 'input', + required: true + },{ + name: 'confirmPassword', label: '{{ _('Confirm Password') }}', + type: 'password', disabled: false, control: 'input', + required: true + }]; + + + alertify.dialog('changeServerPassword' ,function factory() { + return { + main: function(params) { + var title = '{{ _('Change password for ') }}' + params.label + this.set('title', title); + }, + setup:function() { + return { + buttons: [{ + text: '{{ _('Ok') }}', key: 27, className: 'btn btn-primary', attrs:{name:'submit'} + },{ + text: '{{ _('Cancel') }}', key: 27, className: 'btn btn-danger', attrs:{name:'cancel'} + }], + // Set options for dialog + options: { + padding : !1, + overflow: !1, + model: 0, + resizable: true, + maximizable: true, + pinnable: false + } + }; + }, + hooks: { + // triggered when the dialog is closed + onclose: function() { + if (this.view) { + this.view.remove({data: true, internal: true, silent: true}); + } + } + }, + prepare: function() { + var self = this; + // Disable Backup button until user provides Filename + this.__internal.buttons[0].element.disabled = true; + var $container = $("<div class='obj_properties'></div>"); + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + node = d && pgBrowser.Nodes[d._type]; + + if (!d) + return; + + var view = this.view = new Backform.Form({ + el: $container, + model: new newPasswordModel, + fields: passwordChangeFields}); + + view.render(); + + this.elements.content.appendChild($container.get(0)); + + // Listen to model & if filename is provided then enable Backup button + this.view.model.on('change', function() { + var that = this, + password = this.get('password'), + newPassword = this.get('newPassword'), + confirmPassword = this.get('confirmPassword'); + + if (_.isUndefined(password) || _.isNull(password) || password == '' || + _.isUndefined(newPassword) || _.isNull(newPassword) || newPassword == '' || + _.isUndefined(confirmPassword) || _.isNull(confirmPassword) || confirmPassword == '') { + self.__internal.buttons[0].element.disabled = true; + } else if (newPassword != confirmPassword) { + self.__internal.buttons[0].element.disabled = true; + setTimeout(function() { + that.errorModel.set('confirmPassword', '{{ _('Passwords do not match.') }}'); + } ,400); + }else { + that.errorModel.clear(); + self.__internal.buttons[0].element.disabled = false; + } + }); + }, + // Callback functions when click on the buttons of the Alertify dialogs + callback: function(e) { + if (e.button.element.name == "submit") { + var self = this, + args = this.view.model.toJSON(); + + e.cancel = true; + + $.ajax({ + url: url, + method:'POST', + data:{'data': JSON.stringify(args) }, + success: function(res) { + if (res.success) { + alertify.success(res.info); + self.close(); + } else { + alertify.error(res.errormsg); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + alertify.error(err.errormsg); + } + } catch (e) {} + t.unload(i); + } + }); + } + } + }; + }); + } + alertify.changeServerPassword(d).resizeTo('40%','45%'); + return false; } }, model: pgAdmin.Browser.Node.Model.extend({ diff --git a/web/pgadmin/utils/crypto.py b/web/pgadmin/utils/crypto.py index 98411a6..be1b52d 100644 --- a/web/pgadmin/utils/crypto.py +++ b/web/pgadmin/utils/crypto.py @@ -12,6 +12,7 @@ from Crypto.Cipher import AES from Crypto import Random import base64 +import hashlib padding_string = b'}' @@ -68,3 +69,39 @@ def pad(str): # Add padding to make key 32 bytes long return str + ((32 - len(str) % 32) * padding_string) + + +def pqencryptpassword(password, user): + + """ + pqencryptpassword -- to encrypt a password + This is intended to be used by client applications that wish to send + commands like ALTER USER joe PASSWORD 'pwd'. The password need not + be sent in cleartext if it is encrypted on the client side. This is + good because it ensures the cleartext password won't end up in logs, + pg_stat displays, etc. We export the function so that clients won't + be dependent on low-level details like whether the enceyption is MD5 + or something else. + + Arguments are the cleartext password, and the SQL name of the user it + is for. + + Return value is "md5" followed by a 32-hex-digit MD5 checksum.. + + Args: + password: + user: + + Returns: + + """ + + m = hashlib.md5() + + # Place salt at the end because it may be known by users trying to crack + # the MD5 output. + + m.update(password.encode()) + m.update(user.encode()) + + return "md5" + m.hexdigest()
-- Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers