Allow user to change the database connection from an open query tool tab. Fixes #3794

pull/35/head
Nikhil Mohite 2020-10-01 13:29:00 +05:30 committed by Akshay Joshi
parent 228d4bb321
commit be7bb81a19
30 changed files with 2394 additions and 89 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -300,3 +300,24 @@ transaction status by clicking on the status icon in the Query Tool:
.. image:: images/query_tool_connection_status.png
:alt: Query tool connection and transaction statuses
:align: center
Change connection
*****************
User can connect to another server or database from existing open session of query tool.
* Click on the connection link next to connection status.
* Now click on the *<New Connection>* option from the dropdown.
.. image:: images/new_connection_options.png
:alt: Query tool connection options
:align: center
* Now select server, database, user, and role to connect and click OK.
.. image:: images/new_connection_dialog.png
:alt: Query tool connection dialog
:align: center
* A newly created connection will now get listed in the options.
* To connect, select the newly created connection from the dropdown list.

View File

@ -10,6 +10,7 @@ New features
************
| `Issue #1402 <https://redmine.postgresql.org/issues/1402>`_ - Added Macro support.
| `Issue #3794 <https://redmine.postgresql.org/issues/3794>`_ - Allow user to change the database connection from an open query tool tab.
| `Issue #5200 <https://redmine.postgresql.org/issues/5200>`_ - Added support to ignore the owner while comparing objects in the Schema Diff tool
Housekeeping

View File

@ -1247,7 +1247,7 @@ class ServerNode(PGChildNodeView):
}
)
def connect(self, gid, sid):
def connect(self, gid, sid, user_name=None):
"""
Connect the Server and return the connection object.
Verification Process before Connection:
@ -1368,7 +1368,8 @@ class ServerNode(PGChildNodeView):
# not provided, or password has not been saved earlier.
if prompt_password or prompt_tunnel_password:
return self.get_response_for_password(server, 428, prompt_password,
prompt_tunnel_password)
prompt_tunnel_password,
user=user_name)
status = True
try:
@ -1802,7 +1803,8 @@ class ServerNode(PGChildNodeView):
return internal_server_error(errormsg=str(e))
def get_response_for_password(self, server, status, prompt_password=False,
prompt_tunnel_password=False, errmsg=None):
prompt_tunnel_password=False, errmsg=None,
user=None):
if server.use_ssh_tunnel:
return make_json_response(
@ -1829,7 +1831,7 @@ class ServerNode(PGChildNodeView):
result=render_template(
'servers/password.html',
server_label=server.name,
username=server.username,
username=user if user else server.username,
errmsg=errmsg,
service=server.service,
_=gettext,

View File

@ -152,3 +152,38 @@ def delete_role(connection, role_names):
exception = "Error while deleting role: %s: line:%s %s" % (
file_name, sys.exc_traceback.tb_lineno, exception)
print(exception, file=sys.stderr)
def create_role_with_password(server, role_name, role_password):
"""
This function create the role.
:param server:
:param role_name:
:param role_password:
:return:
"""
try:
connection = utils.get_db_connection(server['db'],
server['username'],
server['db_password'],
server['host'],
server['port'],
server['sslmode'])
pg_cursor = connection.cursor()
pg_cursor.execute(
"CREATE ROLE %s LOGIN PASSWORD '%s'" % (role_name, role_password))
connection.commit()
# Get 'oid' from newly created tablespace
pg_cursor.execute(
"SELECT pr.oid from pg_catalog.pg_roles pr WHERE pr.rolname='%s'" %
role_name)
oid = pg_cursor.fetchone()
role_id = ''
if oid:
role_id = oid[0]
connection.close()
return role_id
except Exception as exception:
exception = "Error while deleting role: %s: line:%s %s" % (
file_name, sys.exc_traceback.tb_lineno, exception)
print(exception, file=sys.stderr)

View File

@ -95,6 +95,15 @@ class ServerGroup(db.Model):
name = db.Column(db.String(128), nullable=False)
__table_args__ = (db.UniqueConstraint('user_id', 'name'),)
@property
def serialize(self):
"""Return object data in easily serializable format"""
return {
'id': self.id,
'user_id': self.user_id,
'name': self.name,
}
class Server(db.Model):
"""Define a registered Postgres server"""
@ -176,6 +185,44 @@ class Server(db.Model):
tunnel_password = db.Column(db.String(64), nullable=True)
shared = db.Column(db.Boolean(), nullable=False)
@property
def serialize(self):
"""Return object data in easily serializable format"""
return {
"id": self.id,
"user_id": self.user_id,
"servergroup_id": self.servergroup_id,
"name": self.name,
"host": self.host,
"hostaddr": self.hostaddr,
"port": self.port,
"maintenance_db": self.maintenance_db,
"username": self.username,
"password": self.password,
"save_password": self.save_password,
"role": self.role,
"ssl_mode": self.ssl_mode,
"comment": self.comment,
"discovery_id": self.discovery_id,
"db_res": self.db_res,
"passfile": self.passfile,
"sslcert": self.sslcert,
"sslkey": self.sslkey,
"sslrootcert": self.sslrootcert,
"sslcrl": self.sslcrl,
"sslcompression": self.sslcompression,
"bgcolor": self.bgcolor,
"fgcolor": self.fgcolor,
"service": self.service,
"connect_timeout": self.connect_timeout,
"use_ssh_tunnel": self.use_ssh_tunnel,
"tunnel_host": self.tunnel_host,
"tunnel_port": self.tunnel_port,
"tunnel_authentication": self.tunnel_authentication,
"tunnel_identity_file": self.tunnel_identity_file,
"tunnel_password": self.tunnel_password
}
class ModulePreference(db.Model):
"""Define a preferences table for any modules."""

View File

@ -0,0 +1,262 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2020, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import $ from 'jquery';
import Alertify from 'pgadmin.alertifyjs';
import pgAdmin from 'sources/pgadmin';
import Backform from 'pgadmin.backform';
import newConnectionDialogModel from 'sources/sqleditor/new_connection_dialog_model';
let NewConnectionDialog = {
'dialog': function(handler, reconnect) {
let url = url_for('sqleditor.get_new_connection_data', {
'sid': handler.url_params.sid,
'sgid': handler.url_params.sgid,
});
if(reconnect) {
url += '?connect=1';
}
let title = gettext('Connect to server');
$.ajax({
url: url,
headers: {
'Cache-Control' : 'no-cache',
},
}).done(function (res) {
let response = res.data.result;
response.database_list = [];
response.user_list = [];
if (Alertify.newConnectionDialog) {
delete Alertify.newConnectionDialog;
}
// Create Dialog
Alertify.dialog('newConnectionDialog', function factory() {
let $container = $('<div class=\'new-connection-dialog\'></div>');
return {
main: function(message) {
this.msg = message;
},
build: function() {
this.elements.content.appendChild($container.get(0));
Alertify.pgDialogBuild.apply(this);
},
setup: function(){
return {
buttons: [
{
text: '',
key: 112,
className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button',
attrs: {
name: 'dialog_help',
type: 'button',
label: gettext('Help'),
'aria-label': gettext('Help'),
url: url_for('help.static', {
'filename': 'query_tool.html',
}),
},
},
{
text: gettext('Cancel'),
key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button',
'data-btn-name': 'cancel',
}, {
text: gettext('OK'),
key: 13,
className: 'btn btn-primary fa fa-check pg-alertify-button',
'data-btn-name': 'ok',
},
],
// Set options for dialog
options: {
title: title,
//disable both padding and overflow control.
padding: !1,
overflow: !1,
model: 0,
resizable: true,
maximizable: false,
pinnable: false,
closableByDimmer: false,
modal: false,
autoReset: false,
closable: true,
},
};
},
prepare: function() {
let self = this;
$container.html('');
// Disable Ok button
this.__internal.buttons[2].element.disabled = true;
// Status bar
this.statusBar = $(
'<div class=\'pg-prop-status-bar pg-el-xs-12 d-none\'>' +
' <div class="error-in-footer"> ' +
' <div class="d-flex px-2 py-1"> ' +
' <div class="pr-2"> ' +
' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ' +
' </div> ' +
' <div class="alert-text" role="alert"></div> ' +
' </div> ' +
' </div> ' +
'</div>').appendTo($container);
// To show progress on filter Saving/Updating on AJAX
this.showNewConnectionProgress = $(
`<div id="show_filter_progress" class="pg-sp-container sql-editor-busy-fetching d-none">
<div class="pg-sp-content">
<div class="row"><div class="col-12 pg-sp-icon sql-editor-busy-icon"></div></div>
<div class="row"><div class="col-12 pg-sp-text sql-editor-busy-text">` + gettext('Loading data...') + `</div></div>
</div>
</div>`
).appendTo($container);
$(
self.showNewConnectionProgress[0]
).removeClass('d-none');
self.newConnCollectionModel = newConnectionDialogModel(response, handler.url_params.sgid, handler.url_params.sid);
let fields = Backform.generateViewSchema(null, self.newConnCollectionModel, 'create', null, null, true);
let view = this.view = new Backform.Dialog({
el: '<div></div>',
model: self.newConnCollectionModel,
schema: fields,
});
$(this.elements.body.childNodes[0]).addClass(
'alertify_tools_dialog_properties obj_properties'
);
$container.append(view.render().$el);
// Enable/disable save button and show/hide statusbar based on session
view.listenTo(view.model, 'pgadmin-session:start', function() {
view.listenTo(view.model, 'pgadmin-session:invalid', function(msg) {
self.statusBar.removeClass('d-none');
$(self.statusBar.find('.alert-text')).html(msg);
// Disable Okay button
self.__internal.buttons[2].element.disabled = true;
});
view.listenTo(view.model, 'pgadmin-session:valid', function() {
self.statusBar.addClass('d-none');
$(self.statusBar.find('.alert-text')).html('');
// Enable Okay button
self.__internal.buttons[2].element.disabled = false;
});
});
view.listenTo(view.model, 'pgadmin-session:stop', function() {
view.stopListening(view.model, 'pgadmin-session:invalid');
view.stopListening(view.model, 'pgadmin-session:valid');
});
// Starts monitoring changes to model
view.model.startNewSession();
// Hide Progress ...
$(
self.showNewConnectionProgress[0]
).addClass('d-none');
},
callback: function(e) {
let self = this;
if (e.button.element.name == 'dialog_help') {
e.cancel = true;
pgAdmin.Browser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
null, null);
return;
} else if (e.button['data-btn-name'] === 'ok') {
e.cancel = true; // Do not close dialog
let newConnCollectionModel = this.newConnCollectionModel.toJSON();
let selected_database_name = null;
response.database_list.forEach(function(data){
if(newConnCollectionModel['database'] == data['value']) {
selected_database_name = data['label'];
return false;
}
});
let tab_title = '';
if(newConnCollectionModel['role']) {
tab_title = selected_database_name + '/' + newConnCollectionModel['role'] + '@' + response.server_name;
} else {
tab_title = selected_database_name + '/' + newConnCollectionModel['user'] + '@' + response.server_name;
newConnCollectionModel['role'] = null;
}
let is_create_connection = true;
handler.gridView.connection_list.forEach(function(connection_data){
if(parseInt(connection_data['server']) == newConnCollectionModel['server']
&& parseInt(connection_data['database']) == newConnCollectionModel['database']
&& connection_data['user'] == newConnCollectionModel['user'] && connection_data['role'] == newConnCollectionModel['role']) {
is_create_connection = false;
// break for loop by return false.
return false;
}
if(tab_title == connection_data['title']) {
is_create_connection = false;
return false;
}
});
if(!is_create_connection) {
let errmsg = 'Connection with this configuration already present.';
Alertify.info(errmsg);
}else {
let connection_details = {
'server_group': handler.gridView.handler.url_params.sgid,
'server': newConnCollectionModel['server'],
'database': newConnCollectionModel['database'],
'title': tab_title,
'user': newConnCollectionModel['user'],
'role': newConnCollectionModel['role'],
'password': response.password,
};
handler.gridView.on_change_connection(connection_details, self);
}
} else {
self.close();
}
},
};
});
setTimeout(function(){
Alertify.newConnectionDialog('Connect to server.').resizeTo(pgAdmin.Browser.stdW.md,pgAdmin.Browser.stdH.md);
}, 500);
}).fail(function(error) {
Alertify.alert().setting({
'title': gettext('Connection lost'),
'label':gettext('Ok'),
'message': gettext('Connection to the server has been lost.'),
'onok': function(){
alert(error);
//Close the window after connection is lost
window.close();
},
}).show();
});
},
};
module.exports = NewConnectionDialog;

View File

@ -0,0 +1,339 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2020, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import _ from 'underscore';
import $ from 'jquery';
import pgAdmin from 'sources/pgadmin';
import Backform from 'pgadmin.backform';
import url_for from 'sources/url_for';
import alertify from 'pgadmin.alertifyjs';
export default function newConnectionDialogModel(response, sgid, sid) {
let server_name = '';
let database_name = '';
let NewConnectionSelect2Control = Backform.Select2Control.extend({
fetchData: function(){
let self = this;
let url = self.field.get('url');
url = url_for(url, {
'sid': self.model.attributes.server,
'sgid': sgid,
});
$.ajax({
async: false,
url: url,
headers: {
'Cache-Control' : 'no-cache',
},
}).done(function (res) {
var transform = self.field.get('transform');
if(res.data.status){
let data = res.data.result.data;
if (transform && _.isFunction(transform)) {
self.field.set('options', transform.bind(self, data));
} else {
self.field.set('options', data);
}
} else {
if (transform && _.isFunction(transform)) {
self.field.set('options', transform.bind(self, []));
} else {
self.field.set('options', []);
}
//alertify.error(res.data.msg);
}
}).fail(function(e){
let msg = '';
if(e.status == 404) {
msg = 'Unable to find url.';
} else {
msg = e.responseJSON.errormsg;
}
alertify.error(msg);
});
},
render: function() {
this.fetchData();
return Backform.Select2Control.prototype.render.apply(this, arguments);
},
onChange: function() {
Backform.Select2Control.prototype.onChange.apply(this, arguments);
},
});
let newConnectionModel = pgAdmin.Browser.DataModel.extend({
idAttribute: 'name',
defaults: {
server: parseInt(sid),
database: null,
user: null,
password: null,
server_name: server_name,
database_name: database_name,
},
schema: [{
id: 'server',
name: 'server',
label: gettext('Server'),
type: 'text',
editable: true,
disabled: false,
select2: {
allowClear: false,
},
control: Backform.Select2Control.extend({
connect: function(self) {
let local_self = self;
if(!alertify.connectServer){
alertify.dialog('connectServer', function factory() {
return {
main: function(
title, message, server_id, submit_password=true
) {
this.set('title', title);
this.message = message;
this.server_id = server_id;
this.submit_password = submit_password;
},
setup:function() {
return {
buttons:[{
text: gettext('Cancel'), className: 'btn btn-secondary fa fa-times pg-alertify-button',
key: 27,
},{
text: gettext('OK'), key: 13, className: 'btn btn-primary fa fa-check pg-alertify-button',
}],
focus: {element: '#password', select: true},
options: {
modal: 0, resizable: false, maximizable: false, pinnable: false,
},
};
},
build:function() {
},
prepare:function() {
this.setContent(this.message);
},
callback: function(closeEvent) {
if (closeEvent.button.text == gettext('OK')) {
if(this.submit_password) {
var _url = url_for('sqleditor.connect_server', {'sid': this.server_id});
$.ajax({
type: 'POST',
timeout: 30000,
url: _url,
data: $('#frmPassword').serialize(),
})
.done(function() {
local_self.model.attributes.database = null;
local_self.model.attributes.user = null;
local_self.model.attributes.role = null;
Backform.Select2Control.prototype.onChange.apply(local_self, arguments);
response.server_list.forEach(function(obj){
if(obj.id==self.model.changed.server) {
response.server_name = obj.name;
}
});
})
.fail(function(xhr) {
alertify.connectServer('Connect to server', xhr.responseJSON.result, local_self.getValueFromDOM());
});
} else {
response.password = $('#password').val();
}
} else {
local_self.model.attributes.database = null;
local_self.model.attributes.user = null;
local_self.model.attributes.role = null;
Backform.Select2Control.prototype.onChange.apply(local_self, arguments);
}
closeEvent.close = true;
},
};
});
}
},
render: function() {
let self = this;
self.connect(self);
return Backform.Select2Control.prototype.render.apply(self, arguments);
},
onChange: function() {
this.model.attributes.database = null;
this.model.attributes.user = null;
let self = this;
self.connect(self);
let url = url_for('sqleditor.connect_server', {
'sid': self.getValueFromDOM(),
'usr': self.model.attributes.user,
});
$.ajax({
async: false,
url: url,
type: 'POST',
headers: {
'Cache-Control' : 'no-cache',
},
}).done(function () {
Backform.Select2Control.prototype.onChange.apply(self, arguments);
response.server_list.forEach(function(obj){
if(obj.id==self.model.changed.server) {
response.server_name = obj.name;
}
});
}).fail(function(xhr){
alertify.connectServer('Connect to server', xhr.responseJSON.result, self.getValueFromDOM());
});
},
}),
options: function() {
return _.map(response.server_list, (obj) => {
if (obj.id == parseInt(sid))
response.server_name = obj.name;
return {
value: obj.id,
label: obj.name,
};
});
},
},
{
id: 'database',
name: 'database',
label: gettext('Database'),
type: 'text',
editable: true,
disabled: function(m) {
let self_local = this;
if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server'))
&& m.get('server') !== '') {
setTimeout(function() {
if(self_local.options.length) {
m.set('database', self_local.options[0].value);
}
}, 10);
return false;
}
return true;
},
deps: ['server'],
url: 'sqleditor.get_new_connection_database',
select2: {
allowClear: false,
width: '100%',
first_empty: true,
select_first: false,
},
extraClasses:['new-connection-dialog-style'],
control: NewConnectionSelect2Control,
transform: function(data) {
response.database_list = data;
return data;
},
},
{
id: 'user',
name: 'user',
label: gettext('User'),
type: 'text',
editable: true,
deps: ['server'],
select2: {
allowClear: false,
width: '100%',
},
control: NewConnectionSelect2Control,
url: 'sqleditor.get_new_connection_user',
disabled: function(m) {
let self_local = this;
if (!_.isUndefined(m.get('server')) && !_.isNull(m.get('server'))
&& m.get('server') !== '') {
setTimeout(function() {
if(self_local.options.length) {
m.set('user', self_local.options[0].value);
}
}, 10);
return false;
}
return true;
},
},{
id: 'role',
name: 'role',
label: gettext('Role'),
type: 'text',
editable: true,
deps: ['server'],
select2: {
allowClear: false,
width: '100%',
first_empty: true,
},
control: NewConnectionSelect2Control,
url: 'sqleditor.get_new_connection_role',
disabled: false,
},
/*{
id: 'password',
name: 'password',
label: gettext('Password'tools/sqleditor/__init__.py),
type: 'password',
editable: true,
disabled: true,
deps: ['user'],
control: Backform.InputControl.extend({
render: function() {
let self = this;
self.model.attributes.password = null;
Backform.InputControl.prototype.render.apply(self, arguments);
return self;
},
onChange: function() {
let self = this;
Backform.InputControl.prototype.onChange.apply(self, arguments);
},
}),
},*/
],
validate: function() {
let msg = null;
this.errorModel.clear();
if(_.isUndefined(this.get('database')) || _.isNull(this.get('database'))){
msg = gettext('Please select database');
this.errorModel.set('database', msg);
return msg;
} else if(_.isUndefined(this.get('database')) || _.isUndefined(this.get('user'))|| _.isNull(this.get('user'))) {
msg = gettext('Please select user');
this.errorModel.set('user', msg);
return msg;
}
/*else if((this.attributes.password == '' || _.isUndefined(this.get('password')) || _.isNull(this.get('password')))) {
msg = gettext('Please enter password');
this.errorModel.set('password', msg);
return msg;
}*/
return null;
},
});
let model = new newConnectionModel();
return model;
}

View File

@ -92,6 +92,7 @@
right: 0;
left: 0;
bottom: 0;
z-index: 1;
}
.pg-prop-status-bar {

View File

@ -18,22 +18,23 @@ from flask import Response, url_for, session, request, make_response
from werkzeug.useragents import UserAgent
from flask import current_app as app, render_template
from flask_babelex import gettext
from flask_security import login_required
from flask_security import login_required, current_user
from pgadmin.tools.sqleditor.command import ObjectRegistry, SQLFilter
from pgadmin.tools.sqleditor import check_transaction_status
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_json_response, bad_request, \
internal_server_error
internal_server_error, unauthorized
from config import PG_DEFAULT_DRIVER
from pgadmin.model import Server
from pgadmin.model import Server, User
from pgadmin.utils.driver import get_driver
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost
from pgadmin.utils.preferences import Preferences
from pgadmin.settings import get_setting
from pgadmin.browser.utils import underscore_unescape
from pgadmin.utils.exception import ObjectGone
from pgadmin.utils.constants import MIMETYPE_APP_JS
from pgadmin.tools.sqleditor.utils.macros import get_user_macros
from pgadmin.utils.constants import MIMETYPE_APP_JS, UNAUTH_REQ
MODULE_NAME = 'datagrid'
@ -74,7 +75,8 @@ class DataGridModule(PgAdminModule):
'datagrid.filter_validate',
'datagrid.filter',
'datagrid.panel',
'datagrid.close'
'datagrid.close',
'datagrid.update_query_tool_connection'
]
def on_logout(self, user):
@ -324,10 +326,48 @@ def initialize_query_tool(trans_id, sgid, sid, did=None):
req_args['recreate'] == '1'):
connect = False
is_error, errmsg, conn_id, version = _init_query_tool(trans_id, connect,
sgid, sid, did)
if is_error:
return errmsg
return make_json_response(
data={
'connId': str(conn_id),
'serverVersion': version,
}
)
def _connect(conn, **kwargs):
"""
Connect the database.
:param conn: Connection instance.
:param kwargs: user, role and password data from user.
:return:
"""
user = None
role = None
password = None
is_ask_password = False
if 'user' in kwargs and 'role' in kwargs:
user = kwargs['user']
role = kwargs['role'] if kwargs['role'] else None
password = kwargs['password'] if kwargs['password'] else None
is_ask_password = True
if user:
status, msg = conn.connect(user=user, role=role,
password=password)
else:
status, msg = conn.connect()
return status, msg, is_ask_password, user, role, password
def _init_query_tool(trans_id, connect, sgid, sid, did, **kwargs):
# Create asynchronous connection using random connection id.
conn_id = str(random.randint(1, 9999999))
# Use Maintenance database OID
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
if did is None:
@ -338,24 +378,41 @@ def initialize_query_tool(trans_id, sgid, sid, did=None):
)
except Exception as e:
app.logger.error(e)
return internal_server_error(errormsg=str(e))
return True, internal_server_error(errormsg=str(e)), '', ''
try:
conn = manager.connection(did=did, conn_id=conn_id,
auto_reconnect=False,
use_binary_placeholder=True,
array_to_string=True)
if connect:
status, msg = conn.connect()
status, msg, is_ask_password, user, role, password = _connect(
conn, **kwargs)
if not status:
app.logger.error(msg)
return internal_server_error(errormsg=str(msg))
if is_ask_password:
server = Server.query.filter_by(id=sid).first()
return True, make_json_response(
success=0,
status=428,
result=render_template(
'servers/password.html',
server_label=server.name,
username=user,
errmsg=msg,
_=gettext,
)
), '', ''
else:
return True, internal_server_error(
errormsg=str(msg)), '', ''
except (ConnectionLost, SSHTunnelConnectionLost) as e:
app.logger.error(e)
raise
except Exception as e:
app.logger.error(e)
return internal_server_error(errormsg=str(e))
return True, internal_server_error(errormsg=str(e)), '', ''
if 'gridData' not in session:
sql_grid_data = dict()
@ -377,10 +434,77 @@ def initialize_query_tool(trans_id, sgid, sid, did=None):
# Store the grid dictionary into the session variable
session['gridData'] = sql_grid_data
return False, '', conn_id, manager.version
@blueprint.route(
'/initialize/query_tool/update_connection/<int:trans_id>/'
'<int:sgid>/<int:sid>/<int:did>',
methods=["POST"], endpoint='update_query_tool_connection'
)
def update_query_tool_connection(trans_id, sgid, sid, did):
# Remove transaction Id.
with query_tool_close_session_lock:
data = json.loads(request.data, encoding='utf-8')
if 'gridData' not in session:
return make_json_response(data={'status': True})
grid_data = session['gridData']
# Return from the function if transaction id not found
if str(trans_id) not in grid_data:
return make_json_response(data={'status': True})
connect = True
req_args = request.args
if ('recreate' in req_args and
req_args['recreate'] == '1'):
connect = False
new_trans_id = str(random.randint(1, 9999999))
kwargs = {
'user': data['user'],
'role': data['role'],
'password': data['password'] if 'password' in data else None
}
is_error, errmsg, conn_id, version = _init_query_tool(
new_trans_id, connect, sgid, sid, did, **kwargs)
if is_error:
return errmsg
else:
try:
# Check the transaction and connection status
status, error_msg, conn, trans_obj, session_obj = \
check_transaction_status(trans_id)
status, error_msg, new_conn, new_trans_obj, new_session_obj = \
check_transaction_status(new_trans_id)
new_session_obj['primary_keys'] = session_obj[
'primary_keys'] if 'primary_keys' in session_obj else None
new_session_obj['columns_info'] = session_obj[
'columns_info'] if 'columns_info' in session_obj else None
new_session_obj['client_primary_key'] = session_obj[
'client_primary_key'] if 'client_primary_key'\
in session_obj else None
close_query_tool_session(trans_id)
# Remove the information of unique transaction id from the
# session variable.
grid_data.pop(str(trans_id), None)
session['gridData'] = grid_data
except Exception as e:
app.logger.error(e)
return make_json_response(
data={
'connId': str(conn_id),
'serverVersion': manager.version,
'serverVersion': version,
'tran_id': new_trans_id
}
)

View File

@ -417,8 +417,17 @@
title="" role="img">
</i>
</div>
<div class="editor-title"
style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};">&nbsp;</div>
<div class="connection-info btn-group mr-1" role="group" aria-label="">
<div class="editor-title" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% endif %}; color: {% if fgcolor %}{{ fgcolor }}{% endif %};">&nbsp;
</div>
<span class="conn-info-dd dropdown-toggle dropdown-toggle-split"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<ul class="dropdown-menu" id="connections-list">
</ul>
</div>
</div>
<div id="editor-panel" tabindex="0">
<div id="fetching_data" class="pg-sp-container sql-editor-busy-fetching">
@ -481,6 +490,7 @@ require(['sources/generated/browser_nodes', 'sources/generated/codemirror', 'sou
var script_type_url = '';
{% endif %}
// Start the query tool.
sqlEditorController.start(
{{ uniqueId }},
{{ url_params|safe}},

View File

@ -0,0 +1,134 @@
{
"data_grid_init_query_tool": [
{
"name": "Datagrid init query tool",
"url": "/datagrid/initialize/query_tool/",
"is_positive_test": true,
"mocking_required": false,
"test_data": {},
"mock_data": {},
"expected_data": {
"status_code": 200
}
}
],
"data_grid_query_tool_close": [
{
"name": "Datagrid query tool close",
"url": "/datagrid/close/",
"is_positive_test": true,
"mocking_required": false,
"test_data": {},
"mock_data": {},
"expected_data": {
"status_code": 200
}
}
],
"data_grid_validate_filter": [
{
"name": "Datagrid validate filter",
"url": "/datagrid/filter/validate/",
"is_positive_test": true,
"mocking_required": false,
"test_data": "id = 1",
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Datagrid validate filter",
"url": "/datagrid/filter/validate/",
"is_positive_test": false,
"mocking_required": true,
"test_data": "id = 1",
"mock_data": {
"function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar",
"return_value": "(False, 'Mocked Internal Server Error while validate filter')"
},
"expected_data": {
"status_code": 200
}
}
],
"data_grid_update_connection": [
{
"name": "Datagrid update connection positive",
"url": "/datagrid/initialize/query_tool/update_connection/",
"is_positive_test": true,
"mocking_required": false,
"is_create_role": false,
"test_data": {},
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Datagrid update connection with new user",
"url": "/datagrid/initialize/query_tool/update_connection/",
"is_positive_test": true,
"mocking_required": false,
"is_create_role": true,
"test_data": {},
"mock_data": {},
"expected_data": {
"status_code": 200
}
}
],
"data_grid_panel": [
{
"name": "Datagrid Panel",
"url": "/datagrid/panel/",
"is_positive_test": true,
"mocking_required": false,
"test_data": {},
"mock_data": {
},
"expected_data": {
"status_code": 200
}
}
],
"data_grid_initialize": [
{
"name": "Datagrid Initialize",
"url": "/datagrid/initialize/datagrid/",
"is_positive_test": true,
"mocking_required": false,
"test_data": "id=1",
"mock_data": {
},
"expected_data": {
"status_code": 200
}
},{
"name": "Datagrid Initialize",
"url": "/datagrid/initialize/datagrid/",
"is_positive_test": true,
"mocking_required": false,
"test_data": null,
"mock_data": {},
"expected_data": {
"status_code": 200
}
},
{
"name": "Datagrid Initialize",
"url": "/datagrid/initialize/datagrid/",
"is_positive_test": false,
"mocking_required": true,
"test_data": "id=1",
"mock_data": {
"function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
"return_value": "(False, 'Mocked Internal Server Error while initialize datagrid.')"
},
"expected_data": {
"status_code": 500
}
}
]
}

View File

@ -0,0 +1,73 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
import uuid
import random
from unittest.mock import patch
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.utils.exception import ExecuteError
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from regression.test_setup import config_data
from . import utils as data_grid_utils
class DatagridInitQueryToolTestCase(BaseTestGenerator):
"""
This will init query-tool connection.
"""
scenarios = utils.generate_scenarios(
'data_grid_init_query_tool',
data_grid_utils.test_cases
)
def setUp(self):
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.did = self.database_info["db_id"]
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.sid, self.did)
self.trans_id = str(random.randint(1, 9999999))
if not db_con['data']["connected"]:
raise ExecuteError("Could not connect to database to add a table.")
def init_query_tool(self):
response = self.tester.post(
self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' + str(
self.sid) + '/' + str(self.did),
content_type='html/json'
)
return response
def runTest(self):
""" This function will init query tool connection."""
if self.is_positive_test:
response = self.init_query_tool()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)
def tearDown(self):
"""This function disconnect database."""
database_utils.disconnect_database(self, self.sid,
self.did)

View File

@ -0,0 +1,90 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
import uuid
import random
from unittest.mock import patch
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.utils.exception import ExecuteError
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from regression.test_setup import config_data
from . import utils as data_grid_utils
class DatagridPanelTestCase(BaseTestGenerator):
"""
This will data grid panel.
"""
scenarios = utils.generate_scenarios(
'data_grid_panel',
data_grid_utils.test_cases
)
def setUp(self):
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.did = self.database_info["db_id"]
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.sid, self.did)
if not db_con['data']["connected"]:
raise ExecuteError("Could not connect to database to add a table.")
self.trans_id = str(random.randint(1, 9999999))
qt_init = data_grid_utils._init_query_tool(self, self.trans_id,
self.sgid, self.sid,
self.did)
if not qt_init['success']:
raise ExecuteError("Could not initialize querty tool.")
def panel(self):
query_param = \
'?is_query_tool={0}&sgid={1}&sid={2}&server_type={3}' \
'&did={4}&title={5}'.format(True, self.sgid, self.sid,
self.server_information['type'],
self.did, 'Query panel')
response = self.tester.post(
self.url + str(self.trans_id) + query_param,
data=json.dumps(self.test_data),
content_type='html/json'
)
return response
def runTest(self):
""" This function will update query tool connection."""
if self.is_positive_test:
response = self.panel()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
else:
with patch(self.mock_data["function_name"],
return_value=eval(self.mock_data["return_value"])):
response = self.panel()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)
def tearDown(self):
"""This function disconnect database."""
database_utils.disconnect_database(self, self.sid,
self.did)

View File

@ -0,0 +1,78 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
import uuid
import random
from unittest.mock import patch
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from regression.test_setup import config_data
from . import utils as data_grid_utils
from pgadmin.utils.exception import ExecuteError
class DatagridQueryToolCloseTestCase(BaseTestGenerator):
"""
This will close query-tool connection.
"""
scenarios = utils.generate_scenarios(
'data_grid_query_tool_close',
data_grid_utils.test_cases
)
def setUp(self):
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.did = self.database_info["db_id"]
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.sid, self.did)
if not db_con['data']["connected"]:
raise ExecuteError("Could not connect to database to add a table.")
self.trans_id = str(random.randint(1, 9999999))
qt_init = data_grid_utils._init_query_tool(self, self.trans_id,
self.sgid, self.sid,
self.did)
if not qt_init['success']:
raise ExecuteError("Could not initialize querty tool.")
def close_connection(self):
response = self.tester.delete(
self.url + str(self.trans_id),
content_type='html/json'
)
return response
def runTest(self):
""" This function will update query tool connection."""
if self.is_positive_test:
response = self.close_connection()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)
def tearDown(self):
"""This function disconnect database."""
database_utils.disconnect_database(self, self.sid,
self.did)

View File

@ -0,0 +1,121 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
import uuid
import random
from unittest.mock import patch
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from regression.test_setup import config_data
from pgadmin.browser.server_groups.servers.roles.tests import \
utils as roles_utils
from . import utils as data_grid_utils
from pgadmin.utils.exception import ExecuteError
class DatagridUpdateConnectionTestCase(BaseTestGenerator):
"""
This will update query-tool connection.
"""
scenarios = utils.generate_scenarios(
'data_grid_update_connection',
data_grid_utils.test_cases
)
def setUp(self):
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.did = self.database_info["db_id"]
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.sid, self.did)
self.trans_id = str(random.randint(1, 9999999))
self.roles = None
if self.is_create_role:
data = roles_utils.get_role_data(self.server['db_password'])
self.role_name = data['rolname']
self.role_password = data['rolpassword']
roles_utils.create_role_with_password(
self.server, self.role_name, self.role_password)
if not self.is_positive_test or self.is_create_role:
qt_init = data_grid_utils._init_query_tool(self, self.trans_id,
self.sgid, self.sid,
self.did)
if not qt_init['success']:
raise ExecuteError("Could not initialize querty tool.")
self.test_data = {
"database": self.did,
"server": self.sid,
}
if self.server_information['type'] == 'ppas':
self.test_data['password'] = 'enterprisedb'
self.test_data['user'] = 'enterprisedb'
else:
self.test_data['password'] = 'postgres'
self.test_data['user'] = 'postgres'
if not db_con['data']["connected"]:
raise ExecuteError("Could not connect to database to add a table.")
def update_connection(self, user_data=None):
if user_data:
response = self.tester.post(
self.url + str(self.trans_id) + '/' + str(self.sgid) +
'/' + str(self.sid) + '/' + str(self.did),
data=json.dumps(user_data),
content_type='html/json'
)
else:
response = self.tester.post(
self.url + str(self.trans_id) + '/' + str(self.sgid) + '/' +
str(self.sid) + '/' + str(self.did),
data=json.dumps(self.test_data),
content_type='html/json'
)
return response
def runTest(self):
""" This function will update query tool connection."""
if self.is_positive_test:
user_data = dict()
if self.is_create_role:
user_data['user'] = self.role_name
user_data['password'] = self.role_password
user_data['role'] = None
response = self.update_connection(user_data=user_data)
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
else:
response = self.update_connection()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)
def tearDown(self):
"""This function disconnect database."""
database_utils.disconnect_database(self, self.sid,
self.did)

View File

@ -0,0 +1,92 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
import uuid
import random
from unittest.mock import patch
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from regression.test_setup import config_data
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
import utils as tables_utils
from . import utils as data_grid_utils
from pgadmin.utils.exception import ExecuteError
class DatagridValidateFilterTestCase(BaseTestGenerator):
"""
This will validate filter connection.
"""
scenarios = utils.generate_scenarios(
'data_grid_validate_filter',
data_grid_utils.test_cases
)
def setUp(self):
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.did = self.database_info["db_id"]
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.sid, self.did)
if not db_con['data']["connected"]:
raise ExecuteError("Could not connect to database to add a table.")
self.schema_id = parent_node_dict['schema'][-1]["schema_id"]
self.schema_name = parent_node_dict['schema'][-1]["schema_name"]
schema_response = schema_utils.verify_schemas(self.server,
self.db_name,
self.schema_name)
if not schema_response:
raise ExecuteError("Could not find the schema to add a table.")
self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8])
self.table_id = tables_utils.create_table(self.server, self.db_name,
self.schema_name,
self.table_name)
def validate_filter(self):
response = self.tester.post(
self.url + str(self.sid) + '/' + str(self.did) + '/' +
str(self.table_id),
data=json.dumps(self.test_data),
content_type='html/json'
)
return response
def runTest(self):
""" This function will update query tool connection."""
if self.is_positive_test:
response = self.validate_filter()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
else:
with patch(self.mock_data["function_name"],
return_value=eval(self.mock_data["return_value"])):
response = self.validate_filter()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)
def tearDown(self):
"""This function disconnect database."""
database_utils.disconnect_database(self, self.sid,
self.did)

View File

@ -0,0 +1,109 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
import uuid
import random
from unittest.mock import patch
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from regression.test_setup import config_data
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
import utils as tables_utils
from . import utils as data_grid_utils
from pgadmin.utils.exception import ExecuteError
class DatagridInitializeTestCase(BaseTestGenerator):
"""
This will Initialize datagrid
"""
scenarios = utils.generate_scenarios(
'data_grid_initialize',
data_grid_utils.test_cases
)
def setUp(self):
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.did = self.database_info["db_id"]
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.sid, self.did)
if not db_con['data']["connected"]:
raise ExecuteError("Could not connect to database to add a table.")
self.schema_id = parent_node_dict['schema'][-1]["schema_id"]
self.schema_name = parent_node_dict['schema'][-1]["schema_name"]
schema_response = schema_utils.verify_schemas(self.server,
self.db_name,
self.schema_name)
if not schema_response:
raise ExecuteError("Could not find the schema to add a table.")
self.table_name = "table_for_wizard%s" % (str(uuid.uuid4())[1:8])
self.table_id = tables_utils.create_table(self.server, self.db_name,
self.schema_name,
self.table_name)
self.trans_id = str(random.randint(1, 9999999))
qt_init = data_grid_utils._init_query_tool(self, self.trans_id,
self.sgid, self.sid,
self.did)
if not qt_init['success']:
raise ExecuteError("Could not initialize query tool.")
def initialize_datagrid(self):
if self.test_data:
response = self.tester.post(
self.url + str(self.trans_id) + '/4/table/' +
str(self.sgid) + '/' + str(self.sid) + '/' +
str(self.did) + '/' + str(self.table_id),
data=json.dumps(self.test_data),
content_type='html/json'
)
else:
response = self.tester.post(
self.url + str(self.trans_id) + '/4/table/' +
str(self.sgid) + '/' + str(self.sid) + '/' +
str(self.did) + '/' + str(self.table_id),
content_type='html/json'
)
return response
def runTest(self):
""" This function will update query tool connection."""
if self.is_positive_test:
response = self.initialize_datagrid()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
else:
with patch(self.mock_data["function_name"],
return_value=eval(self.mock_data["return_value"])):
response = self.initialize_datagrid()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)
def tearDown(self):
"""This function disconnect database."""
database_utils.disconnect_database(self, self.sid,
self.did)

View File

@ -0,0 +1,33 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import os
import json
file_name = os.path.basename(__file__)
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
with open(CURRENT_PATH + "/datagrid_test_data.json") as data_file:
test_cases = json.load(data_file)
def _init_query_tool(self, trans_id, server_group, server_id, db_id):
QUERY_TOOL_INIT_URL = '/datagrid/initialize/query_tool'
qt_init = self.tester.post(
'{0}/{1}/{2}/{3}/{4}'.format(
QUERY_TOOL_INIT_URL,
trans_id,
server_group,
server_id,
db_id
),
follow_redirects=True
)
assert qt_init.status_code == 200
qt_init = json.loads(qt_init.data.decode('utf-8'))
return qt_init

View File

@ -10,17 +10,15 @@
"""A blueprint module implementing the sqleditor frame."""
import os
import pickle
import sys
import re
import simplejson as json
from flask import Response, url_for, render_template, session, request, \
current_app
from flask_babelex import gettext
from flask_security import login_required, current_user
from urllib.parse import unquote
import simplejson as json
from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT
from flask import Response, url_for, render_template, session, current_app
from flask import request, jsonify
from flask_babelex import gettext
from flask_security import login_required, current_user
from pgadmin.misc.file_manager import Filemanager
from pgadmin.tools.sqleditor.command import QueryToolCommand
from pgadmin.tools.sqleditor.utils.constant_definition import ASYNC_OK, \
@ -32,11 +30,11 @@ from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \
from pgadmin.utils import PgAdminModule
from pgadmin.utils import get_storage_directory
from pgadmin.utils.ajax import make_json_response, bad_request, \
success_return, internal_server_error, make_response as ajax_response
success_return, internal_server_error
from pgadmin.utils.driver import get_driver
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\
from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost, \
CryptKeyMissing
from pgadmin.utils.menu import MenuItem
from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
register_query_tool_preferences
@ -44,13 +42,16 @@ from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \
read_file_generator
from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog
from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
from pgadmin.utils.constants import MIMETYPE_APP_JS, SERVER_CONNECTION_CLOSED,\
ERROR_MSG_TRANS_ID_NOT_FOUND
from pgadmin.tools.sqleditor.utils.macros import get_macros,\
get_user_macros, set_macros
from pgadmin.utils.constants import MIMETYPE_APP_JS, \
SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, ERROR_FETCHING_DATA
from pgadmin.model import Server
from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
MODULE_NAME = 'sqleditor'
TRANSACTION_STATUS_CHECK_FAILED = gettext("Transaction status check failed.")
_NODES_SQL = 'nodes.sql'
class SqlEditorModule(PgAdminModule):
@ -114,7 +115,13 @@ class SqlEditorModule(PgAdminModule):
'sqleditor.clear_query_history',
'sqleditor.get_macro',
'sqleditor.get_macros',
'sqleditor.set_macros'
'sqleditor.set_macros',
'sqleditor.get_new_connection_data',
'sqleditor.get_new_connection_database',
'sqleditor.get_new_connection_user',
'sqleditor.get_new_connection_role',
'sqleditor.connect_server',
'sqleditor.connect_server_with_user',
]
def register_preferences(self):
@ -230,7 +237,7 @@ def start_view_data(trans_id):
)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
# set fetched row count to 0 as we are executing query again.
trans_obj.update_fetched_row_cnt(0)
@ -376,7 +383,7 @@ def poll(trans_id):
if isinstance(trans_obj, QueryToolCommand):
trans_status = conn.transaction_status()
if trans_status == TX_STATUS_INERROR and \
trans_obj.auto_rollback:
trans_obj.auto_rollback:
conn.execute_void("ROLLBACK;")
st, result = conn.async_fetchmany_2darray(ON_DEMAND_RECORD_COUNT)
@ -686,13 +693,12 @@ def save(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
# If there is no primary key found then return from the function.
if ('primary_keys' not in session_obj or
len(session_obj['primary_keys']) <= 0 or
len(changed_data) <= 0) and \
'has_oids' not in session_obj:
len(session_obj['primary_keys']) <= 0 or
len(changed_data) <= 0) and 'has_oids' not in session_obj:
return make_json_response(
data={
'status': False,
@ -759,7 +765,7 @@ def append_filter_inclusive(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
res = None
filter_sql = ''
@ -813,7 +819,7 @@ def append_filter_exclusive(trans_id):
info='DATAGRID_TRANSACTION_REQUIRED',
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
res = None
filter_sql = ''
@ -866,7 +872,7 @@ def remove_filter(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
res = None
@ -910,7 +916,7 @@ def set_limit(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
res = None
@ -1052,7 +1058,7 @@ def get_object_name(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
res = trans_obj.object_name
else:
status = False
@ -1088,7 +1094,7 @@ def set_auto_commit(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
res = None
@ -1133,7 +1139,7 @@ def set_auto_rollback(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
res = None
@ -1185,7 +1191,7 @@ def auto_complete(trans_id):
status=404)
if status and conn is not None and \
trans_obj is not None and session_obj is not None:
trans_obj is not None and session_obj is not None:
# Create object of SQLAutoComplete class and pass connection object
auto_complete_obj = SQLAutoComplete(
@ -1472,6 +1478,282 @@ def get_filter_data(trans_id):
return FilterDialog.get(status, error_msg, conn, trans_obj, session_ob)
@blueprint.route(
'/new_connection_dialog/<int:sgid>/<int:sid>',
methods=["GET"], endpoint='get_new_connection_data'
)
@login_required
def get_new_connection_data(sgid, sid=None):
"""
This method is used to get required data for get new connection.
:extract_sql_from_network_parameters,
"""
try:
# if sid and not did:
servers = Server.query.all()
server_list = [
{'name': server.serialize['name'], "id": server.serialize['id']}
for server in servers]
msg = "Success"
return make_json_response(
data={
'status': True,
'msg': msg,
'result': {
'server_list': server_list
}
}
)
except Exception:
return make_json_response(
data={
'status': False,
'msg': ERROR_FETCHING_DATA,
'result': {
'server_list': []
}
}
)
@blueprint.route(
'/new_connection_database/<int:sgid>/<int:sid>',
methods=["GET"], endpoint='get_new_connection_database'
)
@login_required
def get_new_connection_database(sgid, sid=None):
"""
This method is used to get required data for get new connection.
:extract_sql_from_network_parameters,
"""
try:
database_list = []
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
if conn.connected():
is_connected = True
else:
is_connected = False
if is_connected:
if sid:
template_path = 'databases/sql/#{0}#'.format(manager.version)
last_system_oid = 0
server_node_res = manager
db_disp_res = None
params = None
if server_node_res and server_node_res.db_res:
db_disp_res = ", ".join(
['%s'] * len(server_node_res.db_res.split(','))
)
params = tuple(server_node_res.db_res.split(','))
sql = render_template(
"/".join([template_path, _NODES_SQL]),
last_system_oid=last_system_oid,
db_restrictions=db_disp_res
)
status, databases = conn.execute_dict(sql, params)
database_list = [
{'label': database['name'], 'value': database['did']} for
database in databases['rows']]
else:
status = False
msg = "Success"
return make_json_response(
data={
'status': status,
'msg': msg,
'result': {
'data': database_list,
}
}
)
else:
return make_json_response(
data={
'status': False,
'msg': SERVER_CONNECTION_CLOSED,
'result': {
'database_list': [],
}
}
)
except Exception:
return make_json_response(
data={
'status': False,
'msg': ERROR_FETCHING_DATA,
'result': {
'database_list': [],
}
}
)
@blueprint.route(
'/new_connection_user/<int:sgid>/<int:sid>',
methods=["GET"], endpoint='get_new_connection_user'
)
@login_required
def get_new_connection_user(sgid, sid=None):
"""
This method is used to get required data for get new connection.
:extract_sql_from_network_parameters,
"""
try:
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
user_list = []
if conn.connected():
is_connected = True
else:
is_connected = False
if is_connected:
if sid:
sql_path = 'roles/sql/#{0}#'.format(manager.version)
status, users = conn.execute_2darray(
render_template(sql_path + _NODES_SQL)
)
user_list = [
{'value': user['rolname'], 'label': user['rolname']} for
user in users['rows'] if user['rolcanlogin']]
else:
status = False
msg = "Success"
return make_json_response(
data={
'status': status,
'msg': msg,
'result': {
'data': user_list,
}
}
)
else:
return make_json_response(
data={
'status': False,
'msg': SERVER_CONNECTION_CLOSED,
'result': {
'user_list': [],
}
}
)
except Exception:
return make_json_response(
data={
'status': False,
'msg': 'Unable to fetch data.',
'result': {
'user_list': [],
}
}
)
@blueprint.route(
'/new_connection_role/<int:sgid>/<int:sid>',
methods=["GET"], endpoint='get_new_connection_role'
)
@login_required
def get_new_connection_role(sgid, sid=None):
"""
This method is used to get required data for get new connection.
:extract_sql_from_network_parameters,
"""
try:
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
role_list = []
if conn.connected():
is_connected = True
else:
is_connected = False
if is_connected:
if sid:
sql_path = 'roles/sql/#{0}#'.format(manager.version)
status, roles = conn.execute_2darray(
render_template(sql_path + _NODES_SQL)
)
role_list = [
{'value': role['rolname'], 'label': role['rolname']} for
role in roles['rows']]
else:
status = False
msg = "Success"
return make_json_response(
data={
'status': status,
'msg': msg,
'result': {
'data': role_list,
}
}
)
else:
return make_json_response(
data={
'status': False,
'msg': SERVER_CONNECTION_CLOSED,
'result': {
'user_list': [],
}
}
)
except Exception:
return make_json_response(
data={
'status': False,
'msg': 'Unable to fetch data.',
'result': {
'user_list': [],
}
}
)
@blueprint.route(
'/connect_server/<int:sid>/<usr>',
methods=["POST"],
endpoint="connect_server_with_user"
)
@blueprint.route(
'/connect_server/<int:sid>',
methods=["POST"],
endpoint="connect_server"
)
@login_required
def connect_server(sid, usr=None):
# Check if server is already connected then no need to reconnect again.
server = Server.query.filter_by(id=sid).first()
driver = get_driver(PG_DEFAULT_DRIVER)
manager = driver.connection_manager(sid)
conn = manager.connection()
user = None
if usr and manager.user != usr:
user = usr
else:
user = manager.user
if conn.connected():
return make_json_response(
success=1,
info=gettext("Server connected."),
data={}
)
view = SchemaDiffRegistry.get_node_view('server')
return view.connect(server.servergroup_id, sid, user_name=user)
@blueprint.route(
'/filter_dialog/<int:trans_id>',
methods=["PUT"], endpoint='set_filter_data'

View File

@ -315,10 +315,6 @@ input.editor-checkbox:focus {
padding: 10px 0px;
}
.editor-title {
width:100%;
}
.connection-status-hide {
display: none !important;
}
@ -396,7 +392,6 @@ input.editor-checkbox:focus {
overflow-y: hidden;
}
/* Macros */
.macro-tab {
@ -424,3 +419,7 @@ input.editor-checkbox:focus {
.macro_dialog .pg-prop-status-bar {
z-index: 1;
}
.new-connection-dialog-style {
width: 100% !important;
}

View File

@ -14,6 +14,7 @@ define('tools.querytool', [
'jqueryui.position', 'underscore', 'pgadmin.alertifyjs',
'sources/pgadmin', 'backbone', 'bundled_codemirror', 'sources/utils',
'pgadmin.misc.explain',
'pgadmin.user_management.current_user',
'sources/selection/grid_selector',
'sources/selection/active_cell_capture',
'sources/selection/clipboard',
@ -26,6 +27,7 @@ define('tools.querytool', [
'sources/sqleditor/execute_query',
'sources/sqleditor/query_tool_http_error_handler',
'sources/sqleditor/filter_dialog',
'sources/sqleditor/new_connection_dialog',
'sources/sqleditor/geometry_viewer',
'sources/sqleditor/history/history_collection.js',
'sources/sqleditor/history/query_history',
@ -53,8 +55,8 @@ define('tools.querytool', [
'pgadmin.tools.user_management',
], function(
gettext, url_for, $, jqueryui, jqueryui_position, _, alertify, pgAdmin, Backbone, codemirror, pgadminUtils,
pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler,
pgExplain, current_user, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, newConnectionHandler,
GeometryViewer, historyColl, queryHist, querySources,
keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid,
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, queryTxnStatus, csrfToken, panelTitleFunc,
@ -98,6 +100,9 @@ define('tools.querytool', [
this.layout = opts.layout;
this.set_server_version(opts.server_ver);
this.trigger('pgadmin-sqleditor:view:initialised');
this.connection_list = [
{'server_group': null,'server': null, 'database': null, 'user': null, 'role': null, 'title': '&lt;New Connection&gt;'},
];
},
// Bind all the events
@ -163,6 +168,35 @@ define('tools.querytool', [
'click .btn-macro': 'on_execute_macro',
},
render_connection: function(data_list) {
if(this.handler.is_query_tool) {
var dropdownElement = document.getElementById('connections-list');
dropdownElement.innerHTML = '';
data_list.forEach((option, index) => {
$('#connections-list').append('<li class="connection-list-item" data-index='+ index +'><a class="dropdown-item" href="#" tabindex="0">'+ option.title +'</a></li>');
});
var self = this;
$('.connection-list-item').click(function() {
self.get_connection_data(this);
});
} else {
$('.conn-info-dd').hide();
$('.editor-title').css({pointerEvents: 'none'});
}
},
get_connection_data: function(event){
var index = $(event).attr('data-index');
var connection_details = this.connection_list[index];
if(connection_details.server_group) {
this.on_change_connection(connection_details);
} else {
this.on_new_connection();
}
},
reflectPreferences: function() {
let self = this,
browser = pgWindow.default.pgAdmin.Browser,
@ -213,6 +247,7 @@ define('tools.querytool', [
set_editor_title: function(title) {
this.$el.find('.editor-title').text(title);
this.render_connection(this.connection_list);
},
// This function is used to render the template.
@ -696,6 +731,8 @@ define('tools.querytool', [
pgBrowser.register_to_activity_listener(document, ()=>{
alertify.alert(gettext('Timeout'), gettext('Your session has timed out due to inactivity. Please close the window and login again.'));
});
self.render_connection(self.connection_list);
},
/* Regarding SlickGrid usage in render_grid function.
@ -1607,6 +1644,17 @@ define('tools.querytool', [
);
},
on_new_connection: function() {
var self = this;
// Trigger the show_filter signal to the SqlEditorController class
self.handler.trigger(
'pgadmin-sqleditor:button:show_new_connection',
self,
self.handler
);
},
// Callback function for include filter button click.
on_include_filter: function(ev) {
var self = this;
@ -2070,6 +2118,83 @@ define('tools.querytool', [
queryToolActions.executeMacro(this.handler, macroId);
},
on_change_connection: function(connection_details, ref) {
let title = this.$el.find('.editor-title').html();
if(connection_details['title'] != title) {
var self = this;
$.ajax({
async: false,
url: url_for('datagrid.update_query_tool_connection', {
'trans_id': self.transId,
'sgid': connection_details['server_group'],
'sid': connection_details['server'],
'did': connection_details['database'],
}),
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(connection_details),
})
.done(function(res) {
if(res.success) {
self.transId = res.data.tran_id;
self.handler.transId = res.data.tran_id;
self.handler.url_params = {
'did': connection_details['database'],
'is_query_tool': self.handler.url_params.is_query_tool,
'server_type': self.handler.url_params.server_type,
'sgid': connection_details['server_group'],
'sid': connection_details['server'],
'title': connection_details['title'],
};
self.set_editor_title(self.handler.url_params.title);
self.handler.setTitle(self.handler.url_params.title);
alertify.success('connected successfully');
if(ref){
let connection_data = {
'server_group': self.handler.url_params.sgid,
'server': connection_details['server'],
'database': connection_details['database'],
'user': connection_details['user'],
'title': connection_details['title'],
'role': connection_details['role'],
'password': connection_details['password'],
'is_allow_new_connection': true,
};
self.connection_list.unshift(connection_data);
self.render_connection(self.connection_list);
ref.close();
}
}
return true;
})
.fail(function(xhr) {
if(xhr.status == 428) {
alertify.connectServer('Connect to server', xhr.responseJSON.result, connection_details['server'], false);
} else {
alertify.error(xhr.responseJSON['errormsg']);
}
/*let url = url_for('sqleditor.connect_server_with_user', {
'sid': newConnCollectionModel['server'],
'usr': newConnCollectionModel['user']
});
$.ajax({
async: false,
url: url,
headers: {
'Cache-Control' : 'no-cache',
},
}).done(function () {
Backform.Select2Control.prototype.onChange.apply(self, arguments);
response.server_list.forEach(function(obj){
if(obj.id==self.model.changed.server) {
response.server_name = obj.name;
}
});
}).fail(function(xhr){});*/
});
}
},
});
@ -2393,6 +2518,17 @@ define('tools.querytool', [
$('#btn-conn-status i').removeClass('obtaining-conn');
self.gridView.set_editor_title(_.unescape(url_params.title));
let connection_data = {
'server_group': self.gridView.handler.url_params.sgid,
'server': self.gridView.handler.url_params.sid,
'database': self.gridView.handler.url_params.did,
'user': null,
'role': null,
'title': _.unescape(url_params.title),
'is_allow_new_connection': false,
};
self.gridView.connection_list.unshift(connection_data);
self.gridView.render_connection(self.gridView.connection_list);
};
pgBrowser.Events.on('pgadmin:query_tool:connected:' + transId, afterConn);
@ -2487,6 +2623,7 @@ define('tools.querytool', [
self.on('pgadmin-sqleditor:button:save_file', self._save_file, self);
self.on('pgadmin-sqleditor:button:deleterow', self._delete, self);
self.on('pgadmin-sqleditor:button:show_filter', self._show_filter, self);
self.on('pgadmin-sqleditor:button:show_new_connection', self._show_new_connection, self);
self.on('pgadmin-sqleditor:button:include_filter', self._include_filter, self);
self.on('pgadmin-sqleditor:button:exclude_filter', self._exclude_filter, self);
self.on('pgadmin-sqleditor:button:remove_filter', self._remove_filter, self);
@ -3696,7 +3833,6 @@ define('tools.querytool', [
}
};
},
// This function will show the filter in the text area.
_show_filter: function() {
let self = this,
@ -3711,7 +3847,19 @@ define('tools.querytool', [
}
FilterHandler.dialog(self, reconnect);
},
// This function will show the new connection.
_show_new_connection: function() {
let self = this,
reconnect = false;
/* When server is disconnected and connected, connection is lost,
* To reconnect pass true
*/
if (arguments.length > 0 && arguments[arguments.length - 1] == 'connect') {
reconnect = true;
}
newConnectionHandler.dialog(self, reconnect);
},
// This function will include the filter by selection.
_include_filter: function() {
var self = this,

View File

@ -30,6 +30,19 @@
color: $sql-title-fg;
}
.connection-info {
background: $sql-title-bg;
color: $sql-title-fg;
width:100%;
display: inherit;
}
.conn-info-dd {
padding-top: 0.3em;
padding-left: 0.2em;
cursor: pointer;
}
#editor-panel {
z-index: 0;

View File

@ -0,0 +1,100 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.test_setup import config_data
from regression.python_test_utils import test_utils as utils
class TestNewConnectionDatabase(BaseTestGenerator):
""" This class will test new connection database. """
API_URL = "/sqleditor/new_connection_database/"
scenarios = [
('New connection dialog',
dict(
url=API_URL,
is_positive_test=True,
mocking_required=False,
is_server_conn_required=False,
test_data={},
mock_data={},
expected_data={
"status_code": 200
}
)),
('New connection dialog connect server',
dict(
url=API_URL,
is_positive_test=True,
mocking_required=False,
is_server_conn_required=True,
test_data={},
mock_data={},
expected_data={
"status_code": 200
}
)),
('New connection dialog negative',
dict(
url=API_URL,
is_positive_test=False,
mocking_required=False,
is_server_conn_required=True,
test_data={},
mock_data={},
expected_data={
"status_code": 200
}
)),
]
def setUp(self):
self.content_type = 'html/json'
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
def get_database(self):
response = self.tester.get(
self.url + str(self.sgid) + '/' + str(self.sid),
content_type=self.content_type
)
return response
def runTest(self):
if self.is_positive_test:
if self.is_server_conn_required:
self.server['password'] = self.server['db_password']
self.tester.post(
'/browser/server/connect/{0}/{1}'.format(
utils.SERVER_GROUP,
self.sid),
data=json.dumps(self.server),
content_type=self.content_type
)
response = self.get_database()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
else:
if self.is_server_conn_required:
self.server['password'] = self.server['db_password']
self.tester.post(
'/browser/server/connect/{0}/{1}'.format(
utils.SERVER_GROUP,
self.sid),
data=json.dumps(self.server),
content_type=self.content_type
)
self.sid = 0
response = self.get_database()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)

View File

@ -0,0 +1,50 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.test_setup import config_data
from regression.python_test_utils import test_utils as utils
class TestNewConnectionDialog(BaseTestGenerator):
""" This class will test new connection dialog. """
scenarios = [
('New connection dialog',
dict(
url="/sqleditor/new_connection_dialog/",
is_positive_test=True,
mocking_required=False,
is_connect_server=False,
test_data={},
mock_data={},
expected_data={
"status_code": 200
}
)),
]
def setUp(self):
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
def new_connection(self):
response = self.tester.get(
self.url + str(self.sgid) + '/' + str(self.sgid),
content_type='html/json'
)
return response
def runTest(self):
if self.is_positive_test:
response = self.new_connection()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)

View File

@ -0,0 +1,100 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import json
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.test_setup import config_data
from regression.python_test_utils import test_utils as utils
class TestNewConnectionUser(BaseTestGenerator):
""" This class will test new connection user. """
API_URL = '/sqleditor/new_connection_user/'
scenarios = [
('New connection dialog',
dict(
url=API_URL,
is_positive_test=True,
mocking_required=False,
is_server_conn_required=False,
test_data={},
mock_data={},
expected_data={
"status_code": 200
}
)),
('New connection dialog connect server',
dict(
url=API_URL,
is_positive_test=True,
mocking_required=False,
is_server_conn_required=True,
test_data={},
mock_data={},
expected_data={
"status_code": 200
}
)),
('New connection dialog negative',
dict(
url=API_URL,
is_positive_test=False,
mocking_required=False,
is_server_conn_required=True,
test_data={},
mock_data={},
expected_data={
"status_code": 200
}
)),
]
def setUp(self):
self.content_type = 'html/json'
self.sid = parent_node_dict["server"][-1]["server_id"]
self.sgid = config_data['server_group']
def get_use(self):
response = self.tester.get(
self.url + str(self.sgid) + '/' + str(self.sid),
content_type=self.content_type
)
return response
def runTest(self):
if self.is_positive_test:
if self.is_server_conn_required:
self.server['password'] = self.server['db_password']
self.tester.post(
'/browser/server/connect/{0}/{1}'.format(
utils.SERVER_GROUP,
self.sid),
data=json.dumps(self.server),
content_type=self.content_type
)
response = self.get_use()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
else:
if self.is_server_conn_required:
self.server['password'] = self.server['db_password']
self.tester.post(
'/browser/server/connect/{0}/{1}'.format(
utils.SERVER_GROUP,
self.sid),
data=json.dumps(self.server),
content_type='html/json'
)
self.sid = 0
response = self.get_use()
actual_response_code = response.status_code
expected_response_code = self.expected_data['status_code']
self.assertEqual(actual_response_code, expected_response_code)

View File

@ -44,3 +44,5 @@ ERROR_MSG_TRANS_ID_NOT_FOUND = gettext(
# Role module constant
ERROR_FETCHING_ROLE_INFORMATION = gettext(
'Error fetching role information from the database server.')
ERROR_FETCHING_DATA = gettext('Unable to fetch data.')

View File

@ -21,7 +21,7 @@ import psycopg2
from flask import g, current_app
from flask_babelex import gettext
from flask_security import current_user
from pgadmin.utils.crypto import decrypt
from pgadmin.utils.crypto import decrypt, encrypt
from psycopg2.extensions import encodings
import config
@ -204,6 +204,45 @@ class Connection(BaseConnection):
def __str__(self):
return self.__repr__()
def _check_user_password(self, kwargs):
"""
Check user and password.
"""
password = None
encpass = None
is_update_password = True
if 'user' in kwargs and kwargs['password']:
password = kwargs['password']
kwargs.pop('password')
is_update_password = False
else:
encpass = kwargs['password'] if 'password' in kwargs else None
return password, encpass, is_update_password
def _decode_password(self, encpass, manager, password, crypt_key):
if encpass:
# Fetch Logged in User Details.
user = User.query.filter_by(id=current_user.id).first()
if user is None:
return True, self.UNAUTHORIZED_REQUEST, password
try:
password = decrypt(encpass, crypt_key)
# password is in bytes, for python3 we need it in string
if isinstance(password, bytes):
password = password.decode()
except Exception as e:
manager.stop_ssh_tunnel()
current_app.logger.exception(e)
return True, \
_(
"Failed to decrypt the saved password.\nError: {0}"
).format(str(e))
return False, '', password
def connect(self, **kwargs):
if self.conn:
if self.conn.closed:
@ -212,11 +251,13 @@ class Connection(BaseConnection):
return True, None
pg_conn = None
password = None
passfile = None
manager = self.manager
crypt_key_present, crypt_key = get_crypt_key()
password, encpass, is_update_password = self._check_user_password(
kwargs)
encpass = kwargs['password'] if 'password' in kwargs else None
passfile = kwargs['passfile'] if 'passfile' in kwargs else None
tunnel_password = kwargs['tunnel_password'] if 'tunnel_password' in \
kwargs else ''
@ -231,38 +272,23 @@ class Connection(BaseConnection):
if manager.use_ssh_tunnel == 1:
manager.check_ssh_tunnel_alive()
if encpass is None:
encpass = self.password or getattr(manager, 'password', None)
if is_update_password:
if encpass is None:
encpass = self.password or getattr(manager, 'password', None)
self.password = encpass
self.password = encpass
# Reset the existing connection password
if self.reconnecting is not False:
self.password = None
crypt_key_present, crypt_key = get_crypt_key()
if not crypt_key_present:
raise CryptKeyMissing()
if encpass:
# Fetch Logged in User Details.
user = User.query.filter_by(id=current_user.id).first()
if user is None:
return False, self.UNAUTHORIZED_REQUEST
try:
password = decrypt(encpass, crypt_key)
# password is in bytes, for python3 we need it in string
if isinstance(password, bytes):
password = password.decode()
except Exception as e:
manager.stop_ssh_tunnel()
current_app.logger.exception(e)
return False, \
_(
"Failed to decrypt the saved password.\nError: {0}"
).format(str(e))
is_error, errmsg, password = self._decode_password(encpass, manager,
password, crypt_key)
if is_error:
return False, errmsg
# If no password credential is found then connect request might
# come from Query tool, ViewData grid, debugger etc tools.
@ -273,7 +299,10 @@ class Connection(BaseConnection):
try:
database = self.db
user = manager.user
if 'user' in kwargs and kwargs['user']:
user = kwargs['user']
else:
user = manager.user
conn_id = self.conn_id
import os
@ -342,10 +371,10 @@ class Connection(BaseConnection):
self.wasConnected = False
raise e
if status:
if status and is_update_password:
manager._update_password(encpass)
else:
if not self.reconnecting:
if not self.reconnecting and is_update_password:
self.wasConnected = False
return status, msg
@ -363,7 +392,7 @@ class Connection(BaseConnection):
else:
self.conn.autocommit = True
def _set_role(self, manager, cur, conn_id):
def _set_role(self, manager, cur, conn_id, **kwargs):
"""
Set role
:param manager:
@ -371,8 +400,18 @@ class Connection(BaseConnection):
:param conn_id:
:return:
"""
if manager.role:
status = self._execute(cur, "SET ROLE TO %s", [manager.role])
is_set_role = False
role = None
if 'role' in kwargs and kwargs['role']:
is_set_role = True
role = kwargs['role']
elif manager.role:
is_set_role = True
role = manager.role
if is_set_role:
status = self._execute(cur, "SET ROLE TO %s", [role])
if status is not None:
self.conn.close()
@ -386,7 +425,7 @@ class Connection(BaseConnection):
msg=status
)
)
return False, \
return True, \
_(
"Failed to setup the role with error message:\n{0}"
).format(status)
@ -449,7 +488,7 @@ class Connection(BaseConnection):
return False, status
is_error, errmsg = self._set_role(manager, cur, conn_id)
is_error, errmsg = self._set_role(manager, cur, conn_id, **kwargs)
if is_error:
return False, errmsg
@ -495,7 +534,7 @@ WHERE db.datname = current_database()""")
if len(manager.db_info) == 1:
manager.did = res['did']
self._set_user_info(cur, manager)
self._set_user_info(cur, manager, **kwargs)
self._set_server_type_and_password(kwargs, manager)
@ -503,7 +542,7 @@ WHERE db.datname = current_database()""")
return True, None
def _set_user_info(self, cur, manager):
def _set_user_info(self, cur, manager, **kwargs):
"""
Set user info.
:param cur:
@ -521,7 +560,7 @@ WHERE db.datname = current_database()""")
WHERE
rolname = current_user""")
if status is None:
if status is None and 'user' not in kwargs:
manager.user_info = dict()
if cur.rowcount > 0:
manager.user_info = cur.fetchmany(1)[0]