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

Reply via email to