diff --git a/docs/en_US/images/role_drop_dialog.png b/docs/en_US/images/role_drop_dialog.png index 43917f9b1..5114b7e52 100644 Binary files a/docs/en_US/images/role_drop_dialog.png and b/docs/en_US/images/role_drop_dialog.png differ diff --git a/docs/en_US/images/role_drop_dialog_sql.png b/docs/en_US/images/role_drop_dialog_sql.png new file mode 100644 index 000000000..c541faa5f Binary files /dev/null and b/docs/en_US/images/role_drop_dialog_sql.png differ diff --git a/docs/en_US/images/role_reassign_dialog.png b/docs/en_US/images/role_reassign_dialog.png index fbf82ac34..53494f2e5 100644 Binary files a/docs/en_US/images/role_reassign_dialog.png and b/docs/en_US/images/role_reassign_dialog.png differ diff --git a/docs/en_US/images/role_reassign_dialog_sql.png b/docs/en_US/images/role_reassign_dialog_sql.png new file mode 100644 index 000000000..8f60f9c06 Binary files /dev/null and b/docs/en_US/images/role_reassign_dialog_sql.png differ diff --git a/docs/en_US/role_reassign_dialog.rst b/docs/en_US/role_reassign_dialog.rst index b5a36f237..324a3fbf8 100644 --- a/docs/en_US/role_reassign_dialog.rst +++ b/docs/en_US/role_reassign_dialog.rst @@ -5,33 +5,45 @@ `Role Reassign/Drop Own Dialog`:index: ************************************** -Use the *Role Reassign/Drop Own* dialog to change the ownership of database objects owned +Use the *Reassign/Drop Own* dialog to change the ownership of database objects owned by a database role. This dialog instructs the system to change the ownership of database objects owned by any of the *old_roles* to *new_role*. -The *Role Reassign/Drop Own* dialog organizes the Reassign & Drop role through General tab. +The *Reassign/Drop Own* dialog organizes the Reassign & Drop role through General tab. .. image:: images/role_reassign_dialog.png - :alt: Role Reassign/Drop Own dialog + :alt: Reassign/Drop Own dialog :align: center -* Use the *Role operation* field to provide Reassign option. +* Use the *Operation* field to provide Reassign option. * Provide a new role in the *Reassign Objects to* field; The ownership of all the objects within the selected database, and of all shared objects (databases, tablespaces), owned by the *old_role* will be reassigned to *new_role*. * Provide a database on which the reassignment is to be carried out. The above example demonstrates reassigning *old_role* to *new_role*. +Click the *SQL* tab to continue. + +.. image:: images/role_reassign_dialog_sql.png + :alt: Reassign/Drop Own dialog sql + :align: center + Removing database objects owned by a database role. .. image:: images/role_drop_dialog.png - :alt: Role Reassign/Drop Own dialog + :alt: Reassign/Drop Own dialog :align: center -* Use the *Role operation* field to provide Drop option. -* Use the *Drop with* field to provide CASCADE option, RESTRICT is default. +* Use the *Operation* field to provide Drop option. +* Use the *Cascade?* field to provide Yes, No is default. * Provide a database on which the drop of objects is to be carried out. +Click the *SQL* tab to continue. + +.. image:: images/role_drop_dialog_sql.png + :alt: Reassign/Drop Own dialog sql + :align: center + The above examples demonstrates drop owned by *role*. * Click the *Help* button (?) to access online help. diff --git a/web/pgadmin/browser/server_groups/servers/roles/__init__.py b/web/pgadmin/browser/server_groups/servers/roles/__init__.py index cfa58a9b5..37e3cbf71 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/roles/__init__.py @@ -1301,6 +1301,61 @@ WHERE return status, res + @check_precondition() + def get_reassign_own_sql(self, gid, sid, rid): + + """ + This function is used to generate sql for reassign/drop role. + + Args: + sid: Server ID + rid: Role Id. + + Returns: Json object with sql generate + """ + + try: + + data = dict() + + if request.data: + data = json.loads(request.data, encoding='utf-8') + else: + rargs = request.args or request.form + for k, v in rargs.items(): + try: + data[k] = json.loads(v, encoding='utf-8') + except ValueError as ve: + data[k] = v + + required_args = ['role_op', 'did', 'old_role_name', + 'new_role_name'] \ + if 'role_op' in data and data['role_op'] == 'reassign' \ + else ['role_op', 'did', 'drop_with_cascade'] + + for arg in required_args: + if arg not in data: + return make_json_response( + data=gettext("-- definition incomplete"), + status=200 + ) + + is_reassign = True if data['role_op'] == 'reassign' else False + data['is_reassign'] = is_reassign + + sql = render_template( + "/".join([self.sql_path, _REASSIGN_OWN_SQL]), + data=data + ) + + return make_json_response( + data=sql.strip('\n'), + status=200 + ) + + except Exception as e: + return False, internal_server_error(errormsg=str(e)) + @check_precondition() def role_reassign_own(self, gid, sid, rid): @@ -1387,8 +1442,9 @@ WHERE return make_json_response( success=1, - info=gettext("Reassign owned successfully!") if is_reassign - else gettext("Drop owned successfully!") + info=gettext("Reassign owned executed successfully!") + if is_reassign + else gettext("Drop owned executed successfully!") ) except Exception as e: diff --git a/web/pgadmin/browser/server_groups/servers/roles/static/js/role.js b/web/pgadmin/browser/server_groups/servers/roles/static/js/role.js index 557e44b7a..dfbda46bc 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/static/js/role.js +++ b/web/pgadmin/browser/server_groups/servers/roles/static/js/role.js @@ -392,7 +392,7 @@ define('pgadmin.node.role', [ var tree = pgBrowser.tree, _i = tree.selected(), _d = _i && _i.length == 1 ? tree.itemData(_i) : undefined, - obj = this, finalUrl; + obj = this, finalUrl, old_role_name; //RoleReassign Model (Objects like role, database) var RoleReassignObjectModel = Backbone.Model.extend({ @@ -402,6 +402,7 @@ define('pgadmin.node.role', [ did: undefined, new_role_id: undefined, new_role_name: undefined, + old_role_name: undefined, drop_with_cascade: false }, @@ -413,7 +414,7 @@ define('pgadmin.node.role', [ schema: [ { id: 'role_op', - label: gettext('Role operation'), + label: gettext('Operation'), cell: 'string', type: 'radioModern', controlsClassName: 'pgadmin-controls col-12 col-sm-8', @@ -538,7 +539,7 @@ define('pgadmin.node.role', [ }, { id: 'drop_with_cascade', - label: gettext('Drop with'), + label: gettext('Cascade?'), cell: 'string', type: 'switch', controlsClassName: 'pgadmin-controls col-12 col-sm-8', @@ -546,8 +547,8 @@ define('pgadmin.node.role', [ disabled: 'isDisabled', group: gettext('General'), options: { - 'onText': gettext('CASCADE'), - 'offText': gettext('RESTRICT'), 'size': 'mini', width: '90' + 'onText': gettext('Yes'), + 'offText': gettext('No'), 'size': 'mini' }, deps: ['role_op'], helpMessage: gettext('Note: CASCADE will automatically drop objects that depend on the affected objects, and in turn all objects that depend on those objects'), @@ -577,6 +578,38 @@ define('pgadmin.node.role', [ }, first_empty: false, helpMessage: gettext('Target database on which the operation will be carried out'), + }, + { + id: 'sqltab', label: gettext('SQL'), group: gettext('SQL'), + type: 'text', disabled: false, control: Backform.SqlTabControl.extend({ + initialize: function() { + // Initialize parent class + Backform.SqlTabControl.prototype.initialize.apply(this, arguments); + }, + onTabChange: function(sql_tab_obj) { + // Fetch the information only if the SQL tab is visible at the moment. + if (this.dialog && sql_tab_obj.shown == this.tabIndex) { + var self = this, + roleReassignData = self.model.toJSON(), + getUrl; + // Add existing role + roleReassignData.old_role_name = old_role_name; + + getUrl = obj.generate_url(_i, 'reassign' , _d, true); + + $.ajax({ + url: getUrl, + type: 'GET', + cache: false, + data: roleReassignData, + dataType: 'json', + contentType: 'application/json', + }).done(function(res) { + self.sqlCtrl.setValue(res.data); + }); + } + }, + }), } ], validate: function() { @@ -668,6 +701,7 @@ define('pgadmin.node.role', [ node = _d && pgBrowser.Nodes[_d._type]; finalUrl = obj.generate_url(_i, 'reassign' , _d, true); + old_role_name = _d.label; if (!_d) return; diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/role_test_data.json b/web/pgadmin/browser/server_groups/servers/roles/tests/role_test_data.json index 918845a2e..47061a4c9 100644 --- a/web/pgadmin/browser/server_groups/servers/roles/tests/role_test_data.json +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/role_test_data.json @@ -123,5 +123,127 @@ "test_result_data": {} } } + ], + "role_reassign_sql": [ + { + "name": "Reassign own role - sql", + "is_positive_test": true, + "test_data": { + "did": null, + "new_role_id": null, + "old_role_name": "role_user_1", + "new_role_name": "role_user_2", + "role_op": "reassign" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": { + "result": "REASSIGN OWNED BY role_user_1 TO role_user_2" + } + } + }, + { + "name": "Reassign own role - sql (SESSION_USER)", + "is_positive_test": true, + "test_data": { + "did": null, + "new_role_id": null, + "role_op": "reassign", + "old_role_name": "role_user_1", + "new_role_name": "SESSION_USER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": { + "result": "REASSIGN OWNED BY role_user_1 TO SESSION_USER" + } + } + }, + { + "name": "Reassign own role - sql (CURRENT_USER)", + "is_positive_test": true, + "test_data": { + "did": null, + "new_role_id": null, + "role_op": "reassign", + "old_role_name": "role_user_1", + "new_role_name": "CURRENT_USER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": { + "result": "REASSIGN OWNED BY role_user_1 TO CURRENT_USER" + } + } + }, + { + "name": "Reassign own role - sql (CURRENT_ROLE)", + "is_positive_test": true, + "test_data": { + "did": null, + "new_role_id": null, + "role_op": "reassign", + "old_role_name": "role_user_1", + "new_role_name": "CURRENT_ROLE" + }, + "server_min_version": 140000, + "skip_msg": "CURRENT_ROLE are not supported by PPAS/PG 13.0 and below.", + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": {} + } + }, + { + "name": "Drop own role - sql (default)", + "is_positive_test": true, + "test_data": { + "did": null, + "new_role_id": null, + "old_role_name": "role_user_1", + "role_op": "drop", + "drop_with_cascade": false + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": { + "result": "DROP OWNED BY role_user_1" + } + } + }, + { + "name": "Drop own role - sql (cascade)", + "is_positive_test": true, + "test_data": { + "did": null, + "new_role_id": null, + "old_role_name": "role_user_1", + "role_op": "drop", + "drop_with_cascade": true + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200, + "error_msg": null, + "test_result_data": { + "result": "DROP OWNED BY role_user_1 CASCADE" + } + } + } ] } diff --git a/web/pgadmin/browser/server_groups/servers/roles/tests/test_role_reassign_own_sql.py b/web/pgadmin/browser/server_groups/servers/roles/tests/test_role_reassign_own_sql.py new file mode 100644 index 000000000..c8b5c8a5b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/roles/tests/test_role_reassign_own_sql.py @@ -0,0 +1,71 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2021, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as roles_utils +import json +from unittest.mock import patch + + +class ReassignRoleSQLTestCase(BaseTestGenerator): + """This class tests the role reassign/drop scenario""" + + url = '/browser/role/reassign/' + + # Generates scenarios + scenarios = utils.generate_scenarios("role_reassign_sql", + roles_utils.test_cases) + + def setUp(self): + self.server_id = parent_node_dict["server"][-1]["server_id"] + self.data = self.test_data + self.role_id = 1 + self.data['did'] = parent_node_dict['database'][-1]['db_id'] + + if hasattr(self, 'server_min_version') and \ + self.server_information['server_version'] \ + < self.server_min_version: + self.skipTest(self.skip_msg) + + def reassign_get_api(self): + + get_response = self.tester.get( + self.url + str(utils.SERVER_GROUP) + '/' + + str(self.server_id) + '/' + str(self.role_id), + data=json.dumps(self.data), + follow_redirects=True) + return get_response + + def runTest(self): + + """This function tests role reassign/drop scenario""" + if self.is_positive_test: + get_response = self.reassign_get_api() + elif self.mocking_required: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + get_response = self.reassign_get_api() + + self.assertEqual(get_response.status_code, + self.expected_data['status_code']) + + self.assertEqual(get_response.json['data'], + self.expected_data['test_result_data']['result']) + + def tearDown(self): + """This function delete the role from added server""" + connection = utils.get_db_connection(self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode']) + connection.close()