Change server password feature

This commit is contained in:
Harshal Dhumal 2016-05-13 13:21:20 +05:30 committed by Akshay Joshi
parent 8e4e1640e7
commit 4816f5ed12
6 changed files with 307 additions and 2 deletions

View File

@ -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,100 @@ class ServerNode(PGChildNodeView):
)
return internal_server_error(errormsg=str(e))
def change_password(self, gid, sid):
"""
This function is used to change the password of the
Database Server.
Args:
gid: Group id
sid: Server id
"""
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',
'change_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)

View File

@ -18,3 +18,7 @@
vertical-align: middle;
height: 1.3em;
}
.change_password {
padding-left: 7px;
}

View File

@ -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,163 @@ 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;
if(!alertify.changeServerPassword) {
var newPasswordModel = Backbone.Model.extend({
defaults: {
user_name: undefined,
password: undefined,
newPassword: undefined,
confirmPassword: undefined
},
validate: function() {
return null;
}
}),
passwordChangeFields = [{
name: 'user_name', label: '{{ _('User') }}',
type: 'text', disabled: true, control: 'input'
},{
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') }} ';
this.set('title', title);
this.user_name = params.user.name;
},
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,
closableByDimmer: false
}
};
},
hooks: {
// triggered when the dialog is closed
onclose: function() {
if (this.view) {
this.view.remove({data: true, internal: true, silent: true});
}
}
},
prepare: function() {
//console.log(this.get('server').user.name);
var self = this;
// Disable Backup button until user provides Filename
this.__internal.buttons[0].element.disabled = true;
var $container = $("<div class='change_password'></div>"),
newpasswordmodel = new newPasswordModel({'user_name': self.user_name});
var view = this.view = new Backform.Form({
el: $container,
model: 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({

View File

@ -0,0 +1,2 @@
{# Change database server password #}
ALTER USER {{conn|qtIdent(user)}} WITH ENCRYPTED PASSWORD {{encrypted_password|qtLiteral}};

View File

@ -0,0 +1,2 @@
{# Change database server password #}
ALTER USER {{conn|qtIdent(user)}} WITH ENCRYPTED PASSWORD {{encrypted_password|qtLiteral}};

View File

@ -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()