Added support for Reassign/Drop Owned for login roles. Fixes #3893
parent
5c6d00d545
commit
5fe52b9cfe
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
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
|
||||
resource_group_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
|
||||
************
|
||||
|
||||
| `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 #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.
|
||||
|
||||
|
|
|
@ -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 config import PG_DEFAULT_DRIVER
|
||||
from flask_babelex import gettext
|
||||
|
||||
_REASSIGN_OWN_SQL = 'reassign_own.sql'
|
||||
|
||||
|
||||
class RoleModule(CollectionNodeModule):
|
||||
|
@ -100,7 +103,7 @@ class RoleView(PGChildNodeView):
|
|||
operations = dict({
|
||||
'obj': [
|
||||
{'get': 'properties', 'delete': 'drop', 'put': 'update'},
|
||||
{'get': 'list', 'post': 'create', 'delete': 'drop'}
|
||||
{'get': 'list', 'post': 'create', 'delete': 'drop'},
|
||||
],
|
||||
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
|
||||
'sql': [{'get': 'sql'}],
|
||||
|
@ -110,6 +113,8 @@ class RoleView(PGChildNodeView):
|
|||
'children': [{'get': 'children'}],
|
||||
'vopts': [{}, {'get': 'voptions'}],
|
||||
'variables': [{'get': 'variables'}],
|
||||
'reassign': [{'get': 'get_reassign_own_sql',
|
||||
'post': 'role_reassign_own'}]
|
||||
})
|
||||
|
||||
def _validate_input_dict_for_new(self, data, req_keys):
|
||||
|
@ -1274,5 +1279,125 @@ WHERE
|
|||
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)
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
define('pgadmin.node.role', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser', 'alertify',
|
||||
'pgadmin.backform', 'select2', 'pgadmin.browser.collection',
|
||||
'pgadmin.browser.node.ui', 'pgadmin.browser.server.variable',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, alertify, Backform) {
|
||||
'pgadmin.backform', 'axios', 'sources/utils', 'backbone', 'select2',
|
||||
'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
|
||||
'pgadmin.browser.server.variable',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, alertify, Backform, axios, utils, Backbone) {
|
||||
|
||||
if (!pgBrowser.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...'),
|
||||
icon: 'wcTabIcon icon-role', data: {action: 'create'},
|
||||
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) {
|
||||
|
@ -373,6 +381,424 @@ define('pgadmin.node.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({
|
||||
idAttribute: 'oid',
|
||||
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 pickle
|
||||
import sys
|
||||
import uuid
|
||||
import json
|
||||
|
||||
from regression.python_test_utils import test_utils as utils
|
||||
from regression.test_setup import config_data
|
||||
|
||||
ROLE_URL = '/browser/role/obj/'
|
||||
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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue