Hi,
PFA updated patch.
--
*Harshal Dhumal*
*Software Engineer *
EenterpriseDB <http://www.enterprisedb.com>
On Thu, May 12, 2016 at 4:14 PM, Akshay Joshi <[email protected]
> 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 <
> [email protected]> 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 <
>> [email protected]> 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 <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> PFA patch for changing server password.
>>>>
>>>>
>>>> --
>>>> *Harshal Dhumal*
>>>> *Software Engineer *
>>>>
>>>>
>>>>
>>>> EenterpriseDB <http://www.enterprisedb.com>
>>>>
>>>
>>>
>>
>>
>> --
>> Sent via pgadmin-hackers mailing list ([email protected])
>> 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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers