mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Change server password feature
This commit is contained in:
parent
8e4e1640e7
commit
4816f5ed12
@ -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)
|
||||
|
@ -18,3 +18,7 @@
|
||||
vertical-align: middle;
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
.change_password {
|
||||
padding-left: 7px;
|
||||
}
|
@ -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({
|
||||
|
@ -0,0 +1,2 @@
|
||||
{# Change database server password #}
|
||||
ALTER USER {{conn|qtIdent(user)}} WITH ENCRYPTED PASSWORD {{encrypted_password|qtLiteral}};
|
@ -0,0 +1,2 @@
|
||||
{# Change database server password #}
|
||||
ALTER USER {{conn|qtIdent(user)}} WITH ENCRYPTED PASSWORD {{encrypted_password|qtLiteral}};
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user