Hi, PFA updated patch.
-- *Harshal Dhumal* *Software Engineer * EenterpriseDB <http://www.enterprisedb.com> On Thu, May 12, 2016 at 4:14 PM, Akshay Joshi <akshay.jo...@enterprisedb.com > wrote: > Hi Harshal > > Below are my review comments : > > - Facing error unexpected identifier 'change_password: function(args)' > in js file > > Fixed > > - > - Please correct the spelling of password in the menu item. > > Fixed > > - > - Title for alertify dialog showing "Change password for servers" it > should be only "Change Password" or "Change Password for <server name>". > > Fixed > > - > - Facing error "name 'decrypt' is not defined." > > Fixed > > - > - Not able to change the password facing error. > > Fixed > > - > > > On Thu, May 12, 2016 at 1:43 PM, Harshal Dhumal < > harshal.dhu...@enterprisedb.com> wrote: > >> Hi, >> >> PFA updated patch. >> >> -- >> *Harshal Dhumal* >> *Software Engineer * >> >> >> >> EenterpriseDB <http://www.enterprisedb.com> >> >> On Thu, May 12, 2016 at 1:27 PM, Harshal Dhumal < >> harshal.dhu...@enterprisedb.com> wrote: >> >>> Hi, >>> This patch needs a rebase. (as "Named restore point" patch is committed.) >>> >>> -- >>> *Harshal Dhumal* >>> *Software Engineer * >>> >>> >>> >>> EenterpriseDB <http://www.enterprisedb.com> >>> >>> On Wed, May 11, 2016 at 12:35 PM, Harshal Dhumal < >>> harshal.dhu...@enterprisedb.com> wrote: >>> >>>> Hi, >>>> >>>> PFA patch for changing server password. >>>> >>>> >>>> -- >>>> *Harshal Dhumal* >>>> *Software Engineer * >>>> >>>> >>>> >>>> EenterpriseDB <http://www.enterprisedb.com> >>>> >>> >>> >> >> >> -- >> Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org) >> To make changes to your subscription: >> http://www.postgresql.org/mailpref/pgadmin-hackers >> >> > > > -- > *Akshay Joshi* > *Principal Software Engineer * > > > > *Phone: +91 20-3058-9517Mobile: +91 976-788-8246* >
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index a79f1d4..130bc16 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 @@ -195,7 +195,9 @@ class ServerNode(PGChildNodeView): [{'post': 'create_restore_point'}], 'connect': [{ 'get': 'connect_status', 'post': 'connect', 'delete': 'disconnect' - }] + }], + 'change_password': [{ + 'post': 'change_password'}] }) def nodes(self, gid): @@ -838,4 +840,93 @@ class ServerNode(PGChildNodeView): ) return internal_server_error(errormsg=str(e)) + 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() + + decrypted_password = decrypt(manager.password, user.password) + + if isinstance(decrypted_password, bytes): + decrypted_password = decrypted_password.decode() + + password = data['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) + + 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 2e8105b..25a1c30 100644 --- a/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/servers.js @@ -48,6 +48,11 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { applies: ['tools', 'context'], callback: 'restore_point', category: 'restore', priority: 7, label: '{{ _('Add named restore point') }}', icon: 'fa fa-anchor', enable : 'is_applicable' + },{ + name: 'change_password', node: 'server', module: this, + applies: ['file'], callback: 'change_password', + label: '{{ _('Change Password...') }}', + icon: 'fa fa-lock', enable : 'is_connected' }]); pgBrowser.messages['PRIV_GRANTEE_NOT_SPECIFIED'] = @@ -259,6 +264,162 @@ function($, _, S, pgAdmin, pgBrowser, alertify) { evt.cancel = false; } ).set({'title':'Restore point name'}); + }, + + /* Change password */ + change_password: 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; + + var info = " {{ _('user') }} '" + d.user.name + ' {{ _('on server') }} ' + d.label; + + if(!alertify.changeServerPassword) { + var newPasswordModel = Backbone.Model.extend({ + defaults: { + password: undefined, + newPassword: undefined, + confirmPassword: undefined + }, + validate: function() { + return null; + } + }), + passwordChangeFields = [{ + name: 'user_name', label: '{{ _('Change password for ') }}', + text: info, + type: 'text', control: 'note' + },{ + 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 user on server') }} ' + 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 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; + + this.errorTimeout && clearTimeout(this.errorTimeout); + this.errorTimeout = 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) {} + } + }); + } + } + }; + }); + } + + alertify.changeServerPassword(d).resizeTo('40%','52%'); + return false; } }, model: pgAdmin.Browser.Node.Model.extend({ diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/sql/9.1_plus/alter_with_encrypted_password.sql b/web/pgadmin/browser/server_groups/servers/templates/servers/sql/9.1_plus/alter_with_encrypted_password.sql new file mode 100644 index 0000000..b18baaf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/sql/9.1_plus/alter_with_encrypted_password.sql @@ -0,0 +1 @@ +ALTER USER {{conn|qtIdent(user)}} WITH ENCRYPTED PASSWORD {{encrypted_password|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/sql/9.2_plus/alter_with_encrypted_password.sql b/web/pgadmin/browser/server_groups/servers/templates/servers/sql/9.2_plus/alter_with_encrypted_password.sql new file mode 100644 index 0000000..b18baaf --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/sql/9.2_plus/alter_with_encrypted_password.sql @@ -0,0 +1 @@ +ALTER USER {{conn|qtIdent(user)}} WITH ENCRYPTED PASSWORD {{encrypted_password|qtLiteral}}; \ No newline at end of file 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