diff --git a/docs/en_US/images/new_connection_dialog.png b/docs/en_US/images/new_connection_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..c2f877dc9771b8b697c925438e4abb4f7436db35 GIT binary patch literal 44942 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 F{{cYfFA@L% literal 0 HcmV?d00001 diff --git a/docs/en_US/images/new_connection_options.png b/docs/en_US/images/new_connection_options.png new file mode 100644 index 0000000000000000000000000000000000000000..3de8fc4ee9a50d1c93c35b274cc21fec784c1138 GIT binary patch literal 47015 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 O-}%!Hr)q!oyY_$PkK4om literal 0 HcmV?d00001 diff --git a/docs/en_US/query_tool.rst b/docs/en_US/query_tool.rst index 83933f0..545fb95 100644 --- a/docs/en_US/query_tool.rst +++ b/docs/en_US/query_tool.rst @@ -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 ** option from the dropdown. + +.. image:: images/new_connection_options.png + :alt: Query tool connection options + :align: center + +* Now select server, database, user, and password 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. diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py index 3a7ee58..028ee64 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/utils.py @@ -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) diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index b33adc0..d24a350 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -94,6 +94,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""" @@ -175,6 +184,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.""" diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js new file mode 100644 index 0000000..a56d736 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog.js @@ -0,0 +1,252 @@ +///////////////////////////////////////////////////////////// +// +// 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 = $('
'); + 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 = $( + '
' + + ' ' + + '
').appendTo($container); + + // To show progress on filter Saving/Updating on AJAX + this.showNewConnectionProgress = $( + `
+
+
+
` + gettext('Loading data...') + `
+
+
` + ).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: '
', + 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 is_create_connection = true; + let title = selected_database_name + '/' + newConnCollectionModel['user'] + '@' + response.server_name; + 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']) { + is_create_connection = false; + // break for loop by return false. + return false; + } + + if(title == connection_data['title']) { + is_create_connection = false; + return false; + } + }); + if(!is_create_connection) { + let errmsg = 'Connection with this configuration already present.'; + Alertify.error(errmsg); + }else { + let connection_details = { + 'server_group': handler.gridView.handler.url_params.sgid, + 'server': newConnCollectionModel['server'], + 'database': newConnCollectionModel['database'], + 'title': title, + 'user': newConnCollectionModel['user'], + 'password': newConnCollectionModel['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; diff --git a/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js new file mode 100644 index 0000000..7f309cd --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/new_connection_dialog_model.js @@ -0,0 +1,228 @@ +///////////////////////////////////////////////////////////// +// +// 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; + url = self.field.get('url'); + + let 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({ + onChange: function() { + this.model.attributes.database = null; + this.model.attributes.user = null; + let self = this; + Backform.Select2Control.prototype.onChange.apply(this, arguments); + + response.server_list.forEach(function(obj){ + if(obj.id==self.model.changed.server) { + response.server_name = obj.name; + } + }); + }, + }), + 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: 'password', + name: 'password', + label: gettext('Password'), + type: 'password', + editable: true, + disabled: false, + 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; +} diff --git a/web/pgadmin/static/scss/_alert.scss b/web/pgadmin/static/scss/_alert.scss index dac552b..836f0af 100644 --- a/web/pgadmin/static/scss/_alert.scss +++ b/web/pgadmin/static/scss/_alert.scss @@ -92,6 +92,7 @@ right: 0; left: 0; bottom: 0; + z-index: 1; } .pg-prop-status-bar { diff --git a/web/pgadmin/tools/datagrid/__init__.py b/web/pgadmin/tools/datagrid/__init__.py index f5fc78c..5f32e74 100644 --- a/web/pgadmin/tools/datagrid/__init__.py +++ b/web/pgadmin/tools/datagrid/__init__.py @@ -18,21 +18,22 @@ 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.utils.constants import MIMETYPE_APP_JS, UNAUTH_REQ MODULE_NAME = 'datagrid' @@ -73,7 +74,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): @@ -320,10 +322,23 @@ 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 _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: @@ -334,7 +349,7 @@ 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, @@ -342,16 +357,26 @@ def initialize_query_tool(trans_id, sgid, sid, did=None): use_binary_placeholder=True, array_to_string=True) if connect: - status, msg = conn.connect() + user = None + password = None + + if 'user' in kwargs and 'password' in kwargs: + user = kwargs['user'] + password = kwargs['password'] + + if user: + status, msg = conn.connect(user=user, password=password) + else: + status, msg = conn.connect() if not status: app.logger.error(msg) - return internal_server_error(errormsg=str(msg)) + 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() @@ -373,10 +398,79 @@ 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//' + '//', + 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 = dict() + if request.data: + 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'], + 'password': data['password'] + } + + 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 internal_server_error(errormsg=str(e)) + return make_json_response( data={ 'connId': str(conn_id), - 'serverVersion': manager.version, + 'serverVersion': version, + 'tran_id': new_trans_id } ) diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html index e81a42a..fb2526f 100644 --- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html +++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html @@ -391,8 +391,17 @@ title="" role="img"> -
 
+
+ + + +
+ +
@@ -455,6 +464,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}}, diff --git a/web/pgadmin/tools/datagrid/tests/__init__.py b/web/pgadmin/tools/datagrid/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json new file mode 100644 index 0000000..a50992f --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/datagrid_test_data.json @@ -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": 500 + } + } + ], + "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 + } + } + ] +} diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py new file mode 100644 index 0000000..6ecf5de --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_init_query_tool.py @@ -0,0 +1,72 @@ +########################################################################## +# +# 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 + + +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 Exception("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) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py new file mode 100644 index 0000000..5ab6a6b --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_panel.py @@ -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 + + +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 Exception("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 Exception("Could not initialize querty tool.") + + def panel(self): + query_param = \ + '?is_query_tool={0}&sgid={1}&sid={2}&server_type={3}' \ + '&did={4}'.format(True, self.sgid, self.sid, + self.server_information['type'], self.did) + + 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) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py new file mode 100644 index 0000000..d1a0a97 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_query_tool_close.py @@ -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 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 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 Exception("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 Exception("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'] = 'root' + self.test_data['user'] = 'postgres' + + 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) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py new file mode 100644 index 0000000..a21c38e --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_update_connection.py @@ -0,0 +1,119 @@ +########################################################################## +# +# 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 + + +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 Exception("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'] = 'root' + self.test_data['user'] = 'postgres' + + if not db_con['data']["connected"]: + raise Exception("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 + 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) diff --git a/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py new file mode 100644 index 0000000..0aba5d8 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_data_grid_validate_filter.py @@ -0,0 +1,91 @@ +########################################################################## +# +# 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 + + +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 Exception("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 Exception("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) diff --git a/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py new file mode 100644 index 0000000..130a1d6 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/test_initialize_data_grid.py @@ -0,0 +1,108 @@ +########################################################################## +# +# 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 + + +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 Exception("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 Exception("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 Exception("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) diff --git a/web/pgadmin/tools/datagrid/tests/utils.py b/web/pgadmin/tools/datagrid/tests/utils.py new file mode 100644 index 0000000..82f9427 --- /dev/null +++ b/web/pgadmin/tools/datagrid/tests/utils.py @@ -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 diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index c62bc7c..434ba88 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -10,33 +10,38 @@ """A blueprint module implementing the sqleditor frame.""" import os import pickle -import sys import re +from urllib.parse import unquote import simplejson as json -from flask import Response, url_for, render_template, session, request, \ - current_app +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 urllib.parse import unquote - -from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT from pgadmin.misc.file_manager import Filemanager from pgadmin.tools.sqleditor.command import QueryToolCommand from pgadmin.tools.sqleditor.utils.constant_definition import ASYNC_OK, \ ASYNC_EXECUTION_ABORTED, \ CONNECTION_STATUS_MESSAGE_MAPPING, TX_STATUS_INERROR +from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog +from pgadmin.tools.sqleditor.utils.query_history import QueryHistory +from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \ + read_file_generator +from pgadmin.tools.sqleditor.utils.query_tool_preferences import \ + register_query_tool_preferences from pgadmin.tools.sqleditor.utils.start_running_query import StartRunningQuery from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \ update_session_grid_transaction 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 + internal_server_error +from pgadmin.utils.constants import MIMETYPE_APP_JS 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 @@ -46,6 +51,8 @@ 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.model import Server + MODULE_NAME = 'sqleditor' @@ -109,6 +116,9 @@ class SqlEditorModule(PgAdminModule): 'sqleditor.get_query_history', 'sqleditor.add_query_history', 'sqleditor.clear_query_history', + 'sqleditor.get_new_connection_data', + 'sqleditor.get_new_connection_database', + 'sqleditor.get_new_connection_user', ] def register_preferences(self): @@ -1465,6 +1475,188 @@ def get_filter_data(trans_id): return FilterDialog.get(status, error_msg, conn, trans_obj, session_ob) +@blueprint.route( + '/new_connection_dialog//', + 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 as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'server_list': [] + } + } + ) + + +@blueprint.route( + '/new_connection_database//', + 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: + # connection = conn.connect() + 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 not connected, ' + 'Please connect with server and try again.', + 'result': { + 'database_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'database_list': [], + } + } + ) + + +@blueprint.route( + '/new_connection_user//', + 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 not connected, ' + 'Please connect with server and try again.', + 'result': { + 'user_list': [], + } + } + ) + except Exception as e: + return make_json_response( + data={ + 'status': False, + 'msg': 'Unable to fetch data.', + 'result': { + 'user_list': [], + } + } + ) + + @blueprint.route( '/filter_dialog/', methods=["PUT"], endpoint='set_filter_data' diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index 00543ff..b8c2d2e 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -315,10 +315,6 @@ input.editor-checkbox:focus { padding: 10px 0px; } -.editor-title { - width:100%; -} - .connection-status-hide { display: none !important; } @@ -395,3 +391,7 @@ input.editor-checkbox:focus { .hide-vertical-scrollbar { overflow-y: hidden; } + +.new-connection-dialog-style { + width: 100% !important; +} diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index ce02a53..9720d36 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -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', @@ -52,8 +54,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, @@ -89,6 +91,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, 'password': null, 'title': '<New Connection>'}, + ]; }, // Bind all the events @@ -151,6 +156,30 @@ define('tools.querytool', [ 'click #btn-rollback': 'on_rollback_transaction', }, + render_connection: function(data_list) { + var dropdownElement = document.getElementById('connections-list'); + dropdownElement.innerHTML = ''; + data_list.forEach((option, index) => { + $('#connections-list').append('
  • '+ option.title +'
  • '); + + }); + var self = this; + $('.connection-list-item').click(function() { + self.get_connection_data(this); + }); + }, + + 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, @@ -199,8 +228,15 @@ define('tools.querytool', [ }); }, - set_editor_title: function(title) { - this.$el.find('.editor-title').text(title); + set_editor_title: function(title, is_connected) { + if(is_connected) { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } else { + this.$el.find('.editor-title').text(title); + this.render_connection(this.connection_list); + } + }, // This function is used to render the template. @@ -684,6 +720,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. @@ -1595,6 +1633,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; @@ -2038,6 +2087,55 @@ define('tools.querytool', [ queryToolActions.executeRollback(this.handler); }, + on_change_connection: function(connection_details, ref) { + var self = this; + $.ajax({ + 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'], + 'password': connection_details['password'], + 'title': connection_details['title'], + 'is_allow_new_connection': true, + }; + self.connection_list.unshift(connection_data); + self.render_connection(self.connection_list); + ref.close(); + } + } + + }) + .fail(function(xhr) { + alertify.error(xhr.responseJSON['errormsg']); + }); + }, }); /* Defining controller class for data grid, which actually @@ -2357,7 +2455,18 @@ define('tools.querytool', [ }); $('#btn-conn-status i').removeClass('obtaining-conn'); - self.gridView.set_editor_title(_.unescape(url_params.title)); + self.gridView.set_editor_title(_.unescape(url_params.title), true); + 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, + 'password': 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); @@ -2452,6 +2561,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); @@ -3622,7 +3732,6 @@ define('tools.querytool', [ } }; }, - // This function will show the filter in the text area. _show_filter: function() { let self = this, @@ -3637,7 +3746,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, diff --git a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss index fd1e5d3..53f2449 100644 --- a/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss +++ b/web/pgadmin/tools/sqleditor/static/scss/_sqleditor.scss @@ -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; diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py new file mode 100644 index 0000000..20fe3e3 --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_database.py @@ -0,0 +1,98 @@ +########################################################################## +# +# 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. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_database/", + 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="/sqleditor/new_connection_database/", + 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="/sqleditor/new_connection_database/", + 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.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='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + 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'] + server_response = 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_database() + actual_response_code = response.status_code + expected_response_code = self.expected_data['status_code'] + self.assertEqual(actual_response_code, expected_response_code) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py new file mode 100644 index 0000000..75a47ef --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_dialog.py @@ -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) diff --git a/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py new file mode 100644 index 0000000..7b6c12e --- /dev/null +++ b/web/pgadmin/tools/sqleditor/tests/test_new_connection_user.py @@ -0,0 +1,98 @@ +########################################################################## +# +# 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. """ + scenarios = [ + ('New connection dialog', + dict( + url="/sqleditor/new_connection_user/", + 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="/sqleditor/new_connection_user/", + 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="/sqleditor/new_connection_user/", + 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.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='html/json' + ) + + return response + + def runTest(self): + if self.is_positive_test: + if self.is_server_conn_required: + self.server['password'] = self.server['db_password'] + server_response = self.tester.post( + '/browser/server/connect/{0}/{1}'.format( + utils.SERVER_GROUP, + self.sid), + data=json.dumps(self.server), + content_type='html/json' + ) + 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'] + server_response = 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) diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py index 9cb65bc..115598a 100644 --- a/web/pgadmin/utils/driver/psycopg2/connection.py +++ b/web/pgadmin/utils/driver/psycopg2/connection.py @@ -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 @@ -211,8 +211,23 @@ class Connection(BaseConnection): password = None passfile = None manager = self.manager + encpass = None + is_update_password = True + crypt_key_present, crypt_key = get_crypt_key() + + if 'user' in kwargs and kwargs['password']: + password = kwargs['password'] + # kwargs['password'] = encrypt(password, crypt_key) + ''' + Remove the password from kwargs as we don't want to update + password in manager when user is connecting through query toll. + If we update password in manager will affect existing connections. + ''' + kwargs.pop('password') + is_update_password = False + else: + encpass = kwargs['password'] if 'password' in kwargs else None - 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 '' @@ -227,16 +242,16 @@ 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() @@ -269,7 +284,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 @@ -338,10 +356,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 @@ -491,7 +509,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) @@ -499,7 +517,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: @@ -517,7 +535,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]