mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support for Reassign/Drop Owned for login roles. Fixes #3893
This commit is contained in:
parent
5c6d00d545
commit
5fe52b9cfe
BIN
docs/en_US/images/role_drop_dialog.png
Normal file
BIN
docs/en_US/images/role_drop_dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
docs/en_US/images/role_reassign_dialog.png
Normal file
BIN
docs/en_US/images/role_reassign_dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
@ -18,4 +18,5 @@ database, right-click on the *Databases* node, and select *Create Database...*
|
|||||||
move_objects
|
move_objects
|
||||||
resource_group_dialog
|
resource_group_dialog
|
||||||
role_dialog
|
role_dialog
|
||||||
tablespace_dialog
|
tablespace_dialog
|
||||||
|
role_reassign_dialog
|
@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
|
|||||||
New features
|
New features
|
||||||
************
|
************
|
||||||
|
|
||||||
|
| `Issue #3893 <https://redmine.postgresql.org/issues/3893>`_ - Added support for Reassign/Drop Owned for login roles.
|
||||||
| `Issue #3920 <https://redmine.postgresql.org/issues/3920>`_ - Do not block the query editor window when running a query.
|
| `Issue #3920 <https://redmine.postgresql.org/issues/3920>`_ - Do not block the query editor window when running a query.
|
||||||
| `Issue #6559 <https://redmine.postgresql.org/issues/6559>`_ - Added option to provide maximum width of the column when 'Resize by data?’ option in the preferences is set to True.
|
| `Issue #6559 <https://redmine.postgresql.org/issues/6559>`_ - Added option to provide maximum width of the column when 'Resize by data?’ option in the preferences is set to True.
|
||||||
|
|
||||||
|
39
docs/en_US/role_reassign_dialog.rst
Normal file
39
docs/en_US/role_reassign_dialog.rst
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
.. _role_reassign_dialog:
|
||||||
|
|
||||||
|
**************************************
|
||||||
|
`Role Reassign/Drop Own Dialog`:index:
|
||||||
|
**************************************
|
||||||
|
|
||||||
|
Use the *Role 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.
|
||||||
|
|
||||||
|
.. image:: images/role_reassign_dialog.png
|
||||||
|
:alt: Role Reassign/Drop Own dialog
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
* Use the *Role 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*.
|
||||||
|
|
||||||
|
Removing database objects owned by a database role.
|
||||||
|
|
||||||
|
.. image:: images/role_drop_dialog.png
|
||||||
|
:alt: Role 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.
|
||||||
|
* Provide a database on which the drop of objects is to be carried out.
|
||||||
|
|
||||||
|
The above examples demonstrates drop owned by *role*.
|
||||||
|
|
||||||
|
* Click the *Help* button (?) to access online help.
|
||||||
|
* Click the *OK* button to save work.
|
||||||
|
* Click the *Cancel* button to exit without saving work.
|
@ -23,6 +23,9 @@ from pgadmin.utils.driver import get_driver
|
|||||||
from pgadmin.utils.constants import ERROR_FETCHING_ROLE_INFORMATION
|
from pgadmin.utils.constants import ERROR_FETCHING_ROLE_INFORMATION
|
||||||
|
|
||||||
from config import PG_DEFAULT_DRIVER
|
from config import PG_DEFAULT_DRIVER
|
||||||
|
from flask_babelex import gettext
|
||||||
|
|
||||||
|
_REASSIGN_OWN_SQL = 'reassign_own.sql'
|
||||||
|
|
||||||
|
|
||||||
class RoleModule(CollectionNodeModule):
|
class RoleModule(CollectionNodeModule):
|
||||||
@ -100,7 +103,7 @@ class RoleView(PGChildNodeView):
|
|||||||
operations = dict({
|
operations = dict({
|
||||||
'obj': [
|
'obj': [
|
||||||
{'get': 'properties', 'delete': 'drop', 'put': 'update'},
|
{'get': 'properties', 'delete': 'drop', 'put': 'update'},
|
||||||
{'get': 'list', 'post': 'create', 'delete': 'drop'}
|
{'get': 'list', 'post': 'create', 'delete': 'drop'},
|
||||||
],
|
],
|
||||||
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||||
'sql': [{'get': 'sql'}],
|
'sql': [{'get': 'sql'}],
|
||||||
@ -110,6 +113,8 @@ class RoleView(PGChildNodeView):
|
|||||||
'children': [{'get': 'children'}],
|
'children': [{'get': 'children'}],
|
||||||
'vopts': [{}, {'get': 'voptions'}],
|
'vopts': [{}, {'get': 'voptions'}],
|
||||||
'variables': [{'get': 'variables'}],
|
'variables': [{'get': 'variables'}],
|
||||||
|
'reassign': [{'get': 'get_reassign_own_sql',
|
||||||
|
'post': 'role_reassign_own'}]
|
||||||
})
|
})
|
||||||
|
|
||||||
def _validate_input_dict_for_new(self, data, req_keys):
|
def _validate_input_dict_for_new(self, data, req_keys):
|
||||||
@ -1274,5 +1279,125 @@ WHERE
|
|||||||
data=res['rows']
|
data=res['rows']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _execute_role_reassign(self, conn, rid=None, data=None):
|
||||||
|
|
||||||
|
"""
|
||||||
|
This function is used for executing reassign/drop
|
||||||
|
query
|
||||||
|
:param conn:
|
||||||
|
:param rid:
|
||||||
|
:param data:
|
||||||
|
:return: status & result object
|
||||||
|
"""
|
||||||
|
|
||||||
|
SQL = render_template(
|
||||||
|
"/".join([self.sql_path, _REASSIGN_OWN_SQL]),
|
||||||
|
rid=rid, data=data
|
||||||
|
)
|
||||||
|
status, res = conn.execute_scalar(SQL)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
return status, res
|
||||||
|
|
||||||
|
return status, res
|
||||||
|
|
||||||
|
@check_precondition()
|
||||||
|
def role_reassign_own(self, gid, sid, rid):
|
||||||
|
|
||||||
|
"""
|
||||||
|
This function is used to reassign/drop role for the selected database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sid: Server ID
|
||||||
|
rid: Role Id.
|
||||||
|
|
||||||
|
Returns: Json object with success/failure status
|
||||||
|
"""
|
||||||
|
if request.data:
|
||||||
|
data = json.loads(request.data, encoding='utf-8')
|
||||||
|
else:
|
||||||
|
data = request.args or request.form
|
||||||
|
|
||||||
|
did = int(data['did'])
|
||||||
|
is_already_connected = False
|
||||||
|
can_disconn = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# Connect to the database where operation needs to carry out.
|
||||||
|
from pgadmin.utils.driver import get_driver
|
||||||
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||||
|
conn = manager.connection(did=did, auto_reconnect=True)
|
||||||
|
is_already_connected = conn.connected()
|
||||||
|
|
||||||
|
pg_db = self.manager.db_info[self.manager.did]
|
||||||
|
|
||||||
|
if did == pg_db['did']:
|
||||||
|
can_disconn = False
|
||||||
|
|
||||||
|
# if database is not connected, try connecting it and get
|
||||||
|
# the connection object.
|
||||||
|
if not is_already_connected:
|
||||||
|
status, errmsg = conn.connect()
|
||||||
|
if not status:
|
||||||
|
current_app.logger.error(
|
||||||
|
"Could not connect to database(#{0}).\nError: {1}"
|
||||||
|
.format(
|
||||||
|
did, errmsg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return internal_server_error(errmsg)
|
||||||
|
else:
|
||||||
|
current_app.logger.info(
|
||||||
|
'Connection Established for Database Id: \
|
||||||
|
%s' % did
|
||||||
|
)
|
||||||
|
|
||||||
|
status, old_role_name = self._execute_role_reassign(conn, rid)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
raise Exception(old_role_name)
|
||||||
|
|
||||||
|
data['old_role_name'] = old_role_name
|
||||||
|
|
||||||
|
is_reassign = True if data['role_op'] == 'reassign' else False
|
||||||
|
data['is_reassign'] = is_reassign
|
||||||
|
|
||||||
|
# check whether role operation is to reassign or drop
|
||||||
|
if is_reassign \
|
||||||
|
and (data['new_role_name'] == 'CURRENT_USER' or
|
||||||
|
data['new_role_name'] == 'SESSION_USER' or
|
||||||
|
data['new_role_name'] == 'CURRENT_ROLE') is False:
|
||||||
|
|
||||||
|
status, new_role_name = \
|
||||||
|
self._execute_role_reassign(conn, data['new_role_id'])
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
raise Exception(new_role_name)
|
||||||
|
|
||||||
|
data['new_role_name'] = new_role_name
|
||||||
|
|
||||||
|
status, res = self._execute_role_reassign(conn, None, data)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
raise Exception(res)
|
||||||
|
|
||||||
|
if is_already_connected is False and can_disconn:
|
||||||
|
manager.release(did=did)
|
||||||
|
|
||||||
|
return make_json_response(
|
||||||
|
success=1,
|
||||||
|
info=gettext("Reassign owned successfully!") if is_reassign
|
||||||
|
else gettext("Drop owned successfully!")
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Release Connection
|
||||||
|
current_app.logger.exception(e)
|
||||||
|
if is_already_connected is False and can_disconn:
|
||||||
|
self.manager.release(did=did)
|
||||||
|
|
||||||
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
|
||||||
RoleView.register_node_view(blueprint)
|
RoleView.register_node_view(blueprint)
|
||||||
|
@ -10,9 +10,10 @@
|
|||||||
define('pgadmin.node.role', [
|
define('pgadmin.node.role', [
|
||||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||||
'sources/pgadmin', 'pgadmin.browser', 'alertify',
|
'sources/pgadmin', 'pgadmin.browser', 'alertify',
|
||||||
'pgadmin.backform', 'select2', 'pgadmin.browser.collection',
|
'pgadmin.backform', 'axios', 'sources/utils', 'backbone', 'select2',
|
||||||
'pgadmin.browser.node.ui', 'pgadmin.browser.server.variable',
|
'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
|
||||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, alertify, Backform) {
|
'pgadmin.browser.server.variable',
|
||||||
|
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, alertify, Backform, axios, utils, Backbone) {
|
||||||
|
|
||||||
if (!pgBrowser.Nodes['coll-role']) {
|
if (!pgBrowser.Nodes['coll-role']) {
|
||||||
pgAdmin.Browser.Nodes['coll-role'] =
|
pgAdmin.Browser.Nodes['coll-role'] =
|
||||||
@ -365,6 +366,13 @@ define('pgadmin.node.role', [
|
|||||||
category: 'create', priority: 4, label: gettext('Login/Group Role...'),
|
category: 'create', priority: 4, label: gettext('Login/Group Role...'),
|
||||||
icon: 'wcTabIcon icon-role', data: {action: 'create'},
|
icon: 'wcTabIcon icon-role', data: {action: 'create'},
|
||||||
enable: 'can_create_role',
|
enable: 'can_create_role',
|
||||||
|
}, {
|
||||||
|
name: 'reassign_role', node: 'role', module: this,
|
||||||
|
applies: ['object', 'context'], callback: 'reassign_role',
|
||||||
|
category: 'role', priority: 5,
|
||||||
|
label: gettext('Reassign/Drop Owned...'),
|
||||||
|
icon: 'wcTabIcon icon-role',
|
||||||
|
enable: 'can_reassign_role',
|
||||||
}]);
|
}]);
|
||||||
},
|
},
|
||||||
can_create_role: function(node, item) {
|
can_create_role: function(node, item) {
|
||||||
@ -373,6 +381,424 @@ define('pgadmin.node.role', [
|
|||||||
|
|
||||||
return server.connected && server.user.can_create_role;
|
return server.connected && server.user.can_create_role;
|
||||||
},
|
},
|
||||||
|
can_reassign_role: function(node, item) {
|
||||||
|
var treeData = this.getTreeNodeHierarchy(item),
|
||||||
|
server = treeData['server'];
|
||||||
|
|
||||||
|
return server.connected && node.can_login;
|
||||||
|
},
|
||||||
|
reassign_role: function() {
|
||||||
|
|
||||||
|
var tree = pgBrowser.tree,
|
||||||
|
_i = tree.selected(),
|
||||||
|
_d = _i && _i.length == 1 ? tree.itemData(_i) : undefined,
|
||||||
|
obj = this, finalUrl;
|
||||||
|
|
||||||
|
//RoleReassign Model (Objects like role, database)
|
||||||
|
var RoleReassignObjectModel = Backbone.Model.extend({
|
||||||
|
idAttribute: 'id',
|
||||||
|
defaults: {
|
||||||
|
role_op: undefined,
|
||||||
|
did: undefined,
|
||||||
|
new_role_id: undefined,
|
||||||
|
new_role_name: undefined,
|
||||||
|
drop_with_cascade: false
|
||||||
|
},
|
||||||
|
|
||||||
|
// Default values!
|
||||||
|
initialize: function() {
|
||||||
|
// Set default options according to node type selection by user
|
||||||
|
Backbone.Model.prototype.initialize.apply(this, arguments);
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
id: 'role_op',
|
||||||
|
label: gettext('Role operation'),
|
||||||
|
cell: 'string',
|
||||||
|
type: 'radioModern',
|
||||||
|
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||||
|
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||||
|
group: gettext('General'),
|
||||||
|
options: [{
|
||||||
|
'label': 'Reassign',
|
||||||
|
'value': 'reassign',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': 'Drop',
|
||||||
|
'value': 'drop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
helpMessage: gettext('Change the ownership or\ndrop the database objects owned by a database role'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'new_role_name',
|
||||||
|
label: gettext('Reassign objects to'),
|
||||||
|
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||||
|
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||||
|
url: 'nodes',
|
||||||
|
helpMessage: gettext('New owner of the affected objects'),
|
||||||
|
transform: function(data, cell) {
|
||||||
|
var res = [],
|
||||||
|
control = cell || this,
|
||||||
|
node = control.field.get('schema_node');
|
||||||
|
|
||||||
|
// remove the current role from list
|
||||||
|
let current_label = control.field.attributes.node_info.role.label;
|
||||||
|
if (data && _.isArray(data)) {
|
||||||
|
|
||||||
|
let CURRENT_USER = {
|
||||||
|
label: 'CURRENT_USER', value: 'CURRENT_USER',
|
||||||
|
image: 'icon-' + node.type, _id: null,
|
||||||
|
},
|
||||||
|
SESSION_USER = {
|
||||||
|
label: 'SESSION_USER', value: 'SESSION_USER', image: 'icon-' + node.type, _id: null,
|
||||||
|
};
|
||||||
|
CURRENT_USER.value = JSON.stringify(CURRENT_USER);
|
||||||
|
SESSION_USER.value = JSON.stringify(SESSION_USER);
|
||||||
|
|
||||||
|
res.push(CURRENT_USER, SESSION_USER);
|
||||||
|
|
||||||
|
if(control.field.attributes.node_data.version >= 140000) {
|
||||||
|
let CURRENT_ROLE = {
|
||||||
|
label: 'CURRENT_ROLE', value: 'CURRENT_ROLE',
|
||||||
|
image: 'icon-' + node.type, _id: null,
|
||||||
|
};
|
||||||
|
CURRENT_ROLE.value = JSON.stringify(CURRENT_ROLE);
|
||||||
|
res.push(CURRENT_ROLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(data, function(d) {
|
||||||
|
/*
|
||||||
|
* d contains json data and sets into
|
||||||
|
* select's option control
|
||||||
|
*
|
||||||
|
* We need to stringify data because formatter will
|
||||||
|
* convert Array Object as [Object] string
|
||||||
|
*/
|
||||||
|
if (current_label != d.label)
|
||||||
|
res.push({label: d.label, image: d.icon, value: JSON.stringify(d)});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
control: Backform.NodeListByIdControl.extend({
|
||||||
|
getValueFromDOM: function() {
|
||||||
|
var data = this.formatter.toRaw(
|
||||||
|
_.unescape(this.$el.find('select').val()), this.model);
|
||||||
|
/*
|
||||||
|
* return null if data is empty to prevent it from
|
||||||
|
* throwing parsing error. Adds check as name can be empty
|
||||||
|
*/
|
||||||
|
if (data === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (typeof(data) === 'string') {
|
||||||
|
data=JSON.parse(data);
|
||||||
|
}
|
||||||
|
return data.label;
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* When name is changed, extract value from its select option and
|
||||||
|
* set attributes values into the model
|
||||||
|
*/
|
||||||
|
onChange: function() {
|
||||||
|
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(
|
||||||
|
this, arguments
|
||||||
|
);
|
||||||
|
var selectedValue = this.$el.find('select').val();
|
||||||
|
if (selectedValue.trim() != '') {
|
||||||
|
var d = this.formatter.toRaw(selectedValue, this.model);
|
||||||
|
if(typeof(d) === 'string')
|
||||||
|
d=JSON.parse(d);
|
||||||
|
this.model.set({
|
||||||
|
'new_role_id' : d._id,
|
||||||
|
'new_role_name': d.label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
node: 'role',
|
||||||
|
group: gettext('General'),
|
||||||
|
select2: {
|
||||||
|
allowClear: false,
|
||||||
|
},
|
||||||
|
disabled: 'isDisabled',
|
||||||
|
deps: ['role_op'],
|
||||||
|
filter: function(d) {
|
||||||
|
// Exclude the currently selected
|
||||||
|
let tree = pgBrowser.tree,
|
||||||
|
_i = tree.selected(),
|
||||||
|
_d = _i && _i.length == 1 ? tree.itemData(_i) : undefined;
|
||||||
|
|
||||||
|
if(!_d)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return d && (d.label != _d.label);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'drop_with_cascade',
|
||||||
|
label: gettext('Drop with'),
|
||||||
|
cell: 'string',
|
||||||
|
type: 'switch',
|
||||||
|
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||||
|
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||||
|
disabled: 'isDisabled',
|
||||||
|
group: gettext('General'),
|
||||||
|
options: {
|
||||||
|
'onText': gettext('CASCADE'),
|
||||||
|
'offText': gettext('RESTRICT'), 'size': 'mini', width: '90'
|
||||||
|
},
|
||||||
|
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'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'did',
|
||||||
|
label: gettext('From database'),
|
||||||
|
controlsClassName: 'pgadmin-controls col-12 col-sm-8',
|
||||||
|
controlLabelClassName: 'control-label col-sm-4 col-12',
|
||||||
|
node: 'database',
|
||||||
|
group: gettext('General'),
|
||||||
|
disabled: 'isDisabled',
|
||||||
|
control: Backform.NodeListByIdControl.extend({
|
||||||
|
onChange: function() {
|
||||||
|
Backform.NodeListByIdControl.prototype.onChange.apply(
|
||||||
|
this, arguments
|
||||||
|
);
|
||||||
|
let did = this.model.get('did');
|
||||||
|
this.model.set('did', parseInt(did));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
select2: {
|
||||||
|
allowClear: false,
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
'select2:select': 'onChange'
|
||||||
|
},
|
||||||
|
first_empty: false,
|
||||||
|
helpMessage: gettext('Target database on which the operation will be carried out'),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
validate: function() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
isDisabled: function(m) {
|
||||||
|
|
||||||
|
let self_local = this;
|
||||||
|
switch(this.name) {
|
||||||
|
case 'new_role_name':
|
||||||
|
return (m.get('role_op') != 'reassign');
|
||||||
|
case 'drop_with_cascade':
|
||||||
|
return (m.get('role_op') != 'drop');
|
||||||
|
case 'did':
|
||||||
|
setTimeout(function() {
|
||||||
|
if(_.isUndefined(m.get('did'))) {
|
||||||
|
let db = self_local.options[0];
|
||||||
|
m.set('did', db.value);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!_d)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!alertify.roleReassignDialog) {
|
||||||
|
alertify.dialog('roleReassignDialog', function factory() {
|
||||||
|
return {
|
||||||
|
main: function(title) {
|
||||||
|
this.set('title', title);
|
||||||
|
},
|
||||||
|
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('Users'),
|
||||||
|
url: url_for('help.static', {'filename': 'role_reassign_dialog.html'})},
|
||||||
|
},{
|
||||||
|
text: gettext('Cancel'),
|
||||||
|
key: 27,
|
||||||
|
className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button',
|
||||||
|
}, {
|
||||||
|
text: gettext('OK'),
|
||||||
|
key: 13,
|
||||||
|
className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
|
||||||
|
}],
|
||||||
|
focus: {
|
||||||
|
element: 0,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
//disable both padding and overflow control.
|
||||||
|
padding : !1,
|
||||||
|
overflow: !1,
|
||||||
|
modal: false,
|
||||||
|
resizable: true,
|
||||||
|
maximizable: true,
|
||||||
|
pinnable: false,
|
||||||
|
closableByDimmer: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
build: function() {
|
||||||
|
alertify.pgDialogBuild.apply(this);
|
||||||
|
},
|
||||||
|
hooks:{
|
||||||
|
onclose: function() {
|
||||||
|
if (this.view) {
|
||||||
|
// clear our backform model/view
|
||||||
|
this.view.remove({data: true, internal: true, silent: true});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prepare:function() {
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
$container = $('<div class=\'role_reassign_own\'></div>');
|
||||||
|
//Disable Okay button
|
||||||
|
self.__internal.buttons[2].element.disabled = true;
|
||||||
|
// Find current/selected node
|
||||||
|
var tree = pgBrowser.tree,
|
||||||
|
_i = tree.selected(),
|
||||||
|
_d = _i && _i.length == 1 ? tree.itemData(_i) : undefined,
|
||||||
|
node = _d && pgBrowser.Nodes[_d._type];
|
||||||
|
|
||||||
|
finalUrl = obj.generate_url(_i, 'reassign' , _d, true);
|
||||||
|
|
||||||
|
if (!_d)
|
||||||
|
return;
|
||||||
|
// Create treeInfo
|
||||||
|
var treeInfo = node.getTreeNodeHierarchy.apply(node, [_i]);
|
||||||
|
// Instance of backbone model
|
||||||
|
var newModel = new RoleReassignObjectModel({}, {node_info: treeInfo}),
|
||||||
|
fields = Backform.generateViewSchema(
|
||||||
|
treeInfo, newModel, 'create', node,
|
||||||
|
treeInfo.server, true
|
||||||
|
);
|
||||||
|
|
||||||
|
var view = self.view = new Backform.Dialog({
|
||||||
|
el: $container, model: newModel, schema: fields,
|
||||||
|
});
|
||||||
|
// Add our class to alertify
|
||||||
|
$(self.elements.body.childNodes[0]).addClass(
|
||||||
|
'alertify_tools_dialog_properties obj_properties'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render dialog
|
||||||
|
view.render();
|
||||||
|
self.elements.content.append($container.get(0));
|
||||||
|
|
||||||
|
const statusBar = $(
|
||||||
|
'<div class=\'pg-prop-status-bar pg-prop-status-bar-absolute 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 class="ml-auto close-error-bar"> ' +
|
||||||
|
' <a aria-label="' + gettext('Close error bar') + '" class="close-error fa fa-times text-danger"></a> ' +
|
||||||
|
' </div> ' +
|
||||||
|
' </div> ' +
|
||||||
|
' </div> ' +
|
||||||
|
'</div>').appendTo($container);
|
||||||
|
|
||||||
|
// Listen to model & if filename is provided then enable Backup button
|
||||||
|
this.view.model.on('change', function() {
|
||||||
|
|
||||||
|
const ctx = this;
|
||||||
|
|
||||||
|
const showError = function(errorField, errormsg) {
|
||||||
|
ctx.errorModel.set(errorField, errormsg);
|
||||||
|
statusBar.removeClass('d-none');
|
||||||
|
statusBar.find('.alert-text').html(errormsg);
|
||||||
|
self.elements.dialog.querySelector('.close-error').addEventListener('click', ()=>{
|
||||||
|
statusBar.addClass('d-none');
|
||||||
|
ctx.errorModel.set(errorField, errormsg);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
statusBar.addClass('d-none');
|
||||||
|
|
||||||
|
if ((this.get('role_op') == 'reassign')
|
||||||
|
&& !_.isUndefined(this.get('new_role_name')
|
||||||
|
&& this.get('new_role_name') !== '')
|
||||||
|
) {
|
||||||
|
this.errorModel.clear();
|
||||||
|
self.__internal.buttons[2].element.disabled = false;
|
||||||
|
} else if(this.get('role_op') == 'drop') {
|
||||||
|
this.errorModel.clear();
|
||||||
|
this.set({'new_role_name': undefined, silent: true});
|
||||||
|
this.set({'new_role_id': undefined, silent: true});
|
||||||
|
self.__internal.buttons[2].element.disabled = false;
|
||||||
|
} else if(_.isUndefined(this.get('new_role_name'))) {
|
||||||
|
let errmsg = gettext('Please provide a new role name');
|
||||||
|
this.errorModel.set('new_role_name', errmsg);
|
||||||
|
showError('new_role_name', errmsg);
|
||||||
|
self.__internal.buttons[2].element.disabled = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.__internal.buttons[2].element.disabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// set default role operation as reassign
|
||||||
|
this.view.model.set({'role_op': 'reassign'});
|
||||||
|
},
|
||||||
|
// Callback functions when click on the buttons of the alertify dialogs
|
||||||
|
callback: function(e) {
|
||||||
|
if (e.button.element.name == 'dialog_help') {
|
||||||
|
e.cancel = true;
|
||||||
|
pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
|
||||||
|
null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.button.text === gettext('OK')) {
|
||||||
|
|
||||||
|
let roleReassignData = this.view.model.toJSON(),
|
||||||
|
roleOp = roleReassignData.role_op,
|
||||||
|
confirmBoxTitle = utils.titleize(roleOp);
|
||||||
|
|
||||||
|
alertify.confirm(
|
||||||
|
gettext('%s Objects', confirmBoxTitle),
|
||||||
|
gettext('Are you sure you wish to %s all the objects owned by the selected role?', roleOp),
|
||||||
|
function() {
|
||||||
|
axios.post(
|
||||||
|
finalUrl,
|
||||||
|
roleReassignData
|
||||||
|
).then(function (response) {
|
||||||
|
if(response.data)
|
||||||
|
alertify.success(response.data.info);
|
||||||
|
}).catch(function (error) {
|
||||||
|
try {
|
||||||
|
const err = error.response.data;
|
||||||
|
alertify.alert(
|
||||||
|
gettext('Role reassign/drop failed.'),
|
||||||
|
err.errormsg
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e.stack || e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() { return true; }
|
||||||
|
).set('labels', {
|
||||||
|
ok: gettext('Yes'),
|
||||||
|
cancel: gettext('No'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
alertify.roleReassignDialog(
|
||||||
|
gettext('Reassign/Drop Owned - \'%s\'', _d.label)
|
||||||
|
).resizeTo(pgAdmin.Browser.stdW.md, pgAdmin.Browser.stdH.lg);
|
||||||
|
},
|
||||||
model: pgAdmin.Browser.Node.Model.extend({
|
model: pgAdmin.Browser.Node.Model.extend({
|
||||||
idAttribute: 'oid',
|
idAttribute: 'oid',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
{# ============= Get the role name using oid ============= #}
|
||||||
|
{% if rid %}
|
||||||
|
SELECT rolname FROM pg_catalog.pg_roles WHERE oid = {{rid}}::oid;
|
||||||
|
{% else %}
|
||||||
|
{# ============= Reassign/Drop own the role ============= #}
|
||||||
|
{% if data %}
|
||||||
|
{% if data.is_reassign %}
|
||||||
|
REASSIGN OWNED BY {{ conn|qtIdent(data.old_role_name) }} TO {% if data.new_role_name == "CURRENT_USER" or data.new_role_name == "SESSION_USER" or data.new_role_name == "CURRENT_ROLE" %}{{ data.new_role_name }}{% else %}{{ conn|qtIdent(data.new_role_name) }}{% endif%}
|
||||||
|
{% else %}
|
||||||
|
DROP OWNED BY {{ conn|qtIdent(data.old_role_name) }}{% if data.drop_with_cascade %} CASCADE{% endif%}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"role_reassign": [
|
||||||
|
{
|
||||||
|
"name": "Reassign own role - Internal server error",
|
||||||
|
"is_positive_test": false,
|
||||||
|
"test_data": {
|
||||||
|
"did": null,
|
||||||
|
"new_role_id": null,
|
||||||
|
"role_op": "reassign"
|
||||||
|
},
|
||||||
|
"mocking_required": true,
|
||||||
|
"mock_data": {
|
||||||
|
"function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.connect",
|
||||||
|
"return_value": "(False, 'Mocked Internal Server Error while checking db connect.')"
|
||||||
|
},
|
||||||
|
"expected_data": {
|
||||||
|
"status_code": 500,
|
||||||
|
"error_msg": null,
|
||||||
|
"test_result_data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Reassign own role",
|
||||||
|
"is_positive_test": true,
|
||||||
|
"test_data": {
|
||||||
|
"did": null,
|
||||||
|
"new_role_id": null,
|
||||||
|
"role_op": "reassign",
|
||||||
|
"new_role_name": null
|
||||||
|
},
|
||||||
|
"mocking_required": false,
|
||||||
|
"mock_data": {},
|
||||||
|
"expected_data": {
|
||||||
|
"status_code": 200,
|
||||||
|
"error_msg": null,
|
||||||
|
"test_result_data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Reassign own role (SESSION_USER)",
|
||||||
|
"is_positive_test": true,
|
||||||
|
"test_data": {
|
||||||
|
"did": null,
|
||||||
|
"new_role_id": null,
|
||||||
|
"role_op": "reassign",
|
||||||
|
"new_role_name": "SESSION_USER"
|
||||||
|
},
|
||||||
|
"mocking_required": false,
|
||||||
|
"mock_data": {},
|
||||||
|
"expected_data": {
|
||||||
|
"status_code": 200,
|
||||||
|
"error_msg": null,
|
||||||
|
"test_result_data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Reassign own role (CURRENT_USER)",
|
||||||
|
"is_positive_test": true,
|
||||||
|
"test_data": {
|
||||||
|
"did": null,
|
||||||
|
"new_role_id": null,
|
||||||
|
"role_op": "reassign",
|
||||||
|
"new_role_name": "CURRENT_USER"
|
||||||
|
},
|
||||||
|
"mocking_required": false,
|
||||||
|
"mock_data": {},
|
||||||
|
"expected_data": {
|
||||||
|
"status_code": 200,
|
||||||
|
"error_msg": null,
|
||||||
|
"test_result_data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Reassign own role (CURRENT_ROLE)",
|
||||||
|
"is_positive_test": true,
|
||||||
|
"test_data": {
|
||||||
|
"did": null,
|
||||||
|
"new_role_id": null,
|
||||||
|
"role_op": "reassign",
|
||||||
|
"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",
|
||||||
|
"is_positive_test": true,
|
||||||
|
"test_data": {
|
||||||
|
"did": null,
|
||||||
|
"new_role_id": null,
|
||||||
|
"role_op": "drop",
|
||||||
|
"drop_with_cascade": false
|
||||||
|
},
|
||||||
|
"mocking_required": false,
|
||||||
|
"mock_data": {},
|
||||||
|
"expected_data": {
|
||||||
|
"status_code": 200,
|
||||||
|
"error_msg": null,
|
||||||
|
"test_result_data": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Drop own role (cascade)",
|
||||||
|
"is_positive_test": true,
|
||||||
|
"test_data": {
|
||||||
|
"did": null,
|
||||||
|
"new_role_id": null,
|
||||||
|
"role_op": "drop",
|
||||||
|
"drop_with_cascade": true
|
||||||
|
},
|
||||||
|
"mocking_required": false,
|
||||||
|
"mock_data": {},
|
||||||
|
"expected_data": {
|
||||||
|
"status_code": 200,
|
||||||
|
"error_msg": null,
|
||||||
|
"test_result_data": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# pgAdmin 4 - PostgreSQL Tools
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||||
|
# This software is released under the PostgreSQL Licence
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from pgadmin.utils.route import BaseTestGenerator
|
||||||
|
from regression import parent_node_dict
|
||||||
|
from regression.python_test_utils import test_utils as utils
|
||||||
|
from pgadmin.utils import server_utils as server_utils
|
||||||
|
from . import utils as roles_utils
|
||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
class ReassignRoleTestCase(BaseTestGenerator):
|
||||||
|
"""This class tests the role reassign/drop scenario"""
|
||||||
|
|
||||||
|
url = '/browser/role/reassign/'
|
||||||
|
|
||||||
|
# Generates scenarios
|
||||||
|
scenarios = utils.generate_scenarios("role_reassign",
|
||||||
|
roles_utils.test_cases)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.server_id = parent_node_dict["server"][-1]["server_id"]
|
||||||
|
|
||||||
|
self.data = self.test_data
|
||||||
|
|
||||||
|
self.role_name = "role_get_%s" % str(uuid.uuid4())[1:8]
|
||||||
|
self.role_id = roles_utils.create_role(self.server, self.role_name)
|
||||||
|
|
||||||
|
role_dict = {
|
||||||
|
"server_id": self.server_id,
|
||||||
|
"role_id": self.role_id,
|
||||||
|
"role_name": self.role_name,
|
||||||
|
"new_role_name": None
|
||||||
|
}
|
||||||
|
|
||||||
|
self.data['did'] = parent_node_dict['database'][-1]['db_id']
|
||||||
|
|
||||||
|
if self.data["role_op"] == 'reassign':
|
||||||
|
|
||||||
|
if hasattr(self, 'server_min_version') and \
|
||||||
|
self.server_information['server_version'] \
|
||||||
|
< self.server_min_version:
|
||||||
|
self.skipTest(self.skip_msg)
|
||||||
|
|
||||||
|
self.role_name_1 = "role_get_%s" % str(uuid.uuid4())[1:8]
|
||||||
|
self.role_id_1 = roles_utils.create_role(self.server,
|
||||||
|
self.role_name_1)
|
||||||
|
role_dict["role_id_1"] = self.role_id_1
|
||||||
|
role_dict["new_role_name"] = self.role_name_1
|
||||||
|
|
||||||
|
self.data["new_role_id"] = self.role_id_1
|
||||||
|
self.data["new_role_name"] = self.role_name_1
|
||||||
|
|
||||||
|
utils.write_node_info("lrid", role_dict)
|
||||||
|
|
||||||
|
def reassign_post_api(self):
|
||||||
|
|
||||||
|
post_response = self.tester.post(
|
||||||
|
self.url + str(utils.SERVER_GROUP) + '/' +
|
||||||
|
str(self.server_id) + '/' + str(self.role_id),
|
||||||
|
data=json.dumps(self.data),
|
||||||
|
follow_redirects=True)
|
||||||
|
return post_response
|
||||||
|
|
||||||
|
def runTest(self):
|
||||||
|
|
||||||
|
"""This function tests role reassign/drop scenario"""
|
||||||
|
if self.is_positive_test:
|
||||||
|
post_response = self.reassign_post_api()
|
||||||
|
elif self.mocking_required:
|
||||||
|
with patch(self.mock_data["function_name"],
|
||||||
|
return_value=eval(self.mock_data["return_value"])):
|
||||||
|
post_response = self.reassign_post_api()
|
||||||
|
|
||||||
|
self.assertEqual(post_response.status_code,
|
||||||
|
self.expected_data['status_code'])
|
||||||
|
|
||||||
|
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'])
|
||||||
|
role_list = [self.role_name]
|
||||||
|
if self.data["role_op"] == 'reassign':
|
||||||
|
role_list.append(self.role_name_1)
|
||||||
|
|
||||||
|
roles_utils.delete_role(connection, role_list)
|
@ -9,16 +9,19 @@
|
|||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pickle
|
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
import json
|
||||||
|
|
||||||
from regression.python_test_utils import test_utils as utils
|
from regression.python_test_utils import test_utils as utils
|
||||||
from regression.test_setup import config_data
|
|
||||||
|
|
||||||
ROLE_URL = '/browser/role/obj/'
|
ROLE_URL = '/browser/role/obj/'
|
||||||
file_name = os.path.basename(__file__)
|
file_name = os.path.basename(__file__)
|
||||||
|
|
||||||
|
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
with open(CURRENT_PATH + "/role_test_data.json") as data_file:
|
||||||
|
test_cases = json.load(data_file)
|
||||||
|
|
||||||
|
|
||||||
def verify_role(server, role_name):
|
def verify_role(server, role_name):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user