mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Show the login roles that are members of a group role be shown when examining a group role. Fixes #5404
This commit is contained in:
parent
68132e2a8f
commit
7bad4ca535
Binary file not shown.
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 80 KiB |
@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #5404 <https://redmine.postgresql.org/issues/5404>`_ - Show the login roles that are members of a group role be shown when examining a group role.
|
||||
| `Issue #6268 <https://redmine.postgresql.org/issues/6268>`_ - Make 'kerberos' an optional feature in the Python wheel, to avoid the need to install MIT Kerberos on the system by default.
|
||||
| `Issue #6270 <https://redmine.postgresql.org/issues/6270>`_ - Added '--replace' option in Import server to replace the list of servers with the newly imported one.
|
||||
|
||||
|
@ -71,11 +71,11 @@ Use the *Privileges* tab to grant privileges to the role.
|
||||
:alt: Role dialog membership tab
|
||||
:align: center
|
||||
|
||||
* Specify members of the role in the *Role Membership* field. Click inside the
|
||||
*Roles* field to select role names from a drop down list. Confirm each
|
||||
selection by checking the checkbox to the right of the role name; delete a
|
||||
selection by clicking the *x* to the left of the role name. Membership conveys
|
||||
the privileges granted to the specified role to each of its members.
|
||||
* Specify member of the role in the *Member of* field and specify the members in the *Member* field.
|
||||
Confirm each selection by checking the checkbox to the right of the role name;
|
||||
delete a selection by clicking the *x* to the left of the role name.
|
||||
Membership conveys the privileges granted to the specified role to each of
|
||||
its members.
|
||||
|
||||
Click the *Parameters* tab to continue.
|
||||
|
||||
|
@ -236,6 +236,105 @@ class RoleView(PGChildNodeView):
|
||||
data['rolmembership'].get('deleted', []),
|
||||
lambda _: True, 'role')
|
||||
|
||||
def _process_rolmembers(self, id, data):
|
||||
"""
|
||||
Parser role members.
|
||||
:param id:
|
||||
:param data:
|
||||
"""
|
||||
def _part_dict_list(dict_list, condition, list_key=None):
|
||||
ret_val = []
|
||||
for d in dict_list:
|
||||
if condition(d):
|
||||
ret_val.append(d[list_key])
|
||||
|
||||
return ret_val
|
||||
if id == -1:
|
||||
data['rol_members'] = []
|
||||
data['rol_admins'] = []
|
||||
|
||||
data['rol_admins'] = _part_dict_list(
|
||||
data['rolmembers'], lambda d: d['admin'], 'role')
|
||||
data['rol_members'] = _part_dict_list(
|
||||
data['rolmembers'], lambda d: not d['admin'], 'role')
|
||||
else:
|
||||
data['rol_admins'] = _part_dict_list(
|
||||
data['rolmembers'].get('added', []),
|
||||
lambda d: d['admin'], 'role')
|
||||
data['rol_members'] = _part_dict_list(
|
||||
data['rolmembers'].get('added', []),
|
||||
lambda d: not d['admin'], 'role')
|
||||
|
||||
data['rol_admins'].extend(_part_dict_list(
|
||||
data['rolmembers'].get('changed', []),
|
||||
lambda d: d['admin'], 'role'))
|
||||
data['rol_revoked_admins'] = _part_dict_list(
|
||||
data['rolmembers'].get('changed', []),
|
||||
lambda d: not d['admin'], 'role')
|
||||
|
||||
data['rol_revoked'] = _part_dict_list(
|
||||
data['rolmembers'].get('deleted', []),
|
||||
lambda _: True, 'role')
|
||||
|
||||
def _validate_rolemembers(self, id, data):
|
||||
"""
|
||||
Validate the rolmembers data dict
|
||||
:param data: role data
|
||||
:return: valid or invalid message
|
||||
"""
|
||||
if 'rolmembers' not in data:
|
||||
return None
|
||||
|
||||
if id == -1:
|
||||
msg = _("""
|
||||
Role members information must be passed as an array of JSON objects in the
|
||||
following format:
|
||||
|
||||
rolmembers:[{
|
||||
role: [rolename],
|
||||
admin: True/False
|
||||
},
|
||||
...
|
||||
]""")
|
||||
|
||||
if not self._validate_input_dict_for_new(data['rolmembers'],
|
||||
['role', 'admin']):
|
||||
return msg
|
||||
|
||||
self._process_rolmembers(id, data)
|
||||
return None
|
||||
|
||||
msg = _("""
|
||||
Role membership information must be passed as a string representing an array of
|
||||
JSON objects in the following format:
|
||||
rolmembers:{
|
||||
'added': [{
|
||||
role: [rolename],
|
||||
admin: True/False
|
||||
},
|
||||
...
|
||||
],
|
||||
'deleted': [{
|
||||
role: [rolename],
|
||||
admin: True/False
|
||||
},
|
||||
...
|
||||
],
|
||||
'updated': [{
|
||||
role: [rolename],
|
||||
admin: True/False
|
||||
},
|
||||
...
|
||||
]
|
||||
""")
|
||||
if not self._validate_input_dict_for_update(data['rolmembers'],
|
||||
['role', 'admin'],
|
||||
['role']):
|
||||
return msg
|
||||
|
||||
self._process_rolmembers(id, data)
|
||||
return None
|
||||
|
||||
def _validate_rolemembership(self, id, data):
|
||||
"""
|
||||
Validate the rolmembership data dict
|
||||
@ -433,7 +532,7 @@ rolmembership:{
|
||||
'rolcanlogin', 'rolsuper', 'rolcreatedb',
|
||||
'rolcreaterole', 'rolinherit', 'rolreplication',
|
||||
'rolcatupdate', 'variables', 'rolmembership',
|
||||
'seclabels'
|
||||
'seclabels', 'rolmembers'
|
||||
]:
|
||||
data[key] = json.loads(val, encoding='utf-8')
|
||||
else:
|
||||
@ -466,6 +565,11 @@ rolmembership:{
|
||||
if invalid_msg is not None:
|
||||
return precondition_required(invalid_msg)
|
||||
|
||||
invalid_msg = self._validate_rolemembers(
|
||||
kwargs.get('rid', -1), data)
|
||||
if invalid_msg is not None:
|
||||
return precondition_required(invalid_msg)
|
||||
|
||||
self.request = data
|
||||
|
||||
return f(self, **kwargs)
|
||||
@ -716,6 +820,16 @@ rolmembership:{
|
||||
})
|
||||
row['seclabels'] = res
|
||||
|
||||
if 'rolmembers' in row:
|
||||
rolmembers = []
|
||||
for role in row['rolmembers']:
|
||||
role = re.search(r'([01])(.+)', role)
|
||||
rolmembers.append({
|
||||
'role': role.group(2),
|
||||
'admin': True if role.group(1) == '1' else False
|
||||
})
|
||||
row['rolmembers'] = rolmembers
|
||||
|
||||
@check_precondition(action='properties')
|
||||
def properties(self, gid, sid, rid):
|
||||
|
||||
|
@ -90,7 +90,7 @@ define('pgadmin.node.role', [
|
||||
template: _.template([
|
||||
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
||||
'<div class="<%=Backform.controlsClassName%>">',
|
||||
' <select title = <%=label%> multiple="multiple" style="width:100%;" class="pgadmin-controls <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-JSON.stringify(value)%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%>>',
|
||||
' <select title = <%=label%> multiple="multiple" style="width:100%;" class="pgadmin-controls <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-JSON.stringify(value)%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly" : ""%> <%=required ? "required" : ""%>>',
|
||||
' <% for (var i=0; i < options.length; i++) { %>',
|
||||
' <% var option = options[i]; %>',
|
||||
' <option value=<%-option.value%> data-icon=<%-option.image%> <%=value != null && _.indexOf(value, option.value) != -1 ? "selected" : ""%> <%=option.disabled ? "disabled=\'disabled\'" : ""%>><%-option.label%></option>',
|
||||
@ -107,7 +107,7 @@ define('pgadmin.node.role', [
|
||||
' <span><%= opttext %><span>',
|
||||
' <% if (checkbox) { %>',
|
||||
' <div class="custom-control custom-checkbox custom-checkbox-no-label d-inline">',
|
||||
' <input tabindex="-1" type="checkbox" class="custom-control-input" id="check_<%= opttext %>" />',
|
||||
' <input tabindex="-1" type="checkbox" class="custom-control-input" id="check_<%= opttext %>" <%=disabled ? "disabled" : ""%> />',
|
||||
' <label class="custom-control-label" for="check_<%= opttext %>">',
|
||||
' <span class="sr-only">WITH ADMIN<span>',
|
||||
' </label>',
|
||||
@ -149,6 +149,7 @@ define('pgadmin.node.role', [
|
||||
_.extend(data, {
|
||||
disabled: evalF(data.disabled, data, this.model),
|
||||
visible: evalF(data.visible, data, this.model),
|
||||
readonly: evalF(data.readonly, data, this.model),
|
||||
required: evalF(data.required, data, this.model),
|
||||
helpMessage: evalASFunc(data.helpMessage, data, this.model),
|
||||
});
|
||||
@ -242,7 +243,7 @@ define('pgadmin.node.role', [
|
||||
multiple: true,
|
||||
tags: true,
|
||||
allowClear: data.disabled ? false : true,
|
||||
placeholder: data.disabled ? '' : gettext('Select members'),
|
||||
placeholder: data.disabled ? '' : gettext('Select roles'),
|
||||
width: 'style',
|
||||
}).on('change', function(e) {
|
||||
$(e.target).find(':selected').each(function() {
|
||||
@ -386,6 +387,7 @@ define('pgadmin.node.role', [
|
||||
rolcatupdate: false,
|
||||
rolreplication: false,
|
||||
rolmembership: [],
|
||||
rolmembers: [],
|
||||
rolvaliduntil: null,
|
||||
seclabels: [],
|
||||
variables: [],
|
||||
@ -492,11 +494,12 @@ define('pgadmin.node.role', [
|
||||
controlsClassName: 'pgadmin-controls pg-el-sm-8 pg-el-12',
|
||||
min_version: 90100,
|
||||
readonly: 'readonly',
|
||||
},{
|
||||
id: 'rolmembership', label: gettext('Roles'),
|
||||
},
|
||||
{
|
||||
id: 'rolmembership', label: gettext('Member of'),
|
||||
group: gettext('Membership'), type: 'collection',
|
||||
cell: 'string', readonly: 'readonly',
|
||||
mode: ['properties', 'edit', 'create'],
|
||||
cell: 'string', mode: ['properties', 'edit', 'create'],
|
||||
readonly: 'readonly',
|
||||
control: RoleMembersControl, model: pgBrowser.Node.Model.extend({
|
||||
keys: ['role'],
|
||||
idAttribute: 'role',
|
||||
@ -518,7 +521,34 @@ define('pgadmin.node.role', [
|
||||
return gettext('Roles shown with a check mark have the WITH ADMIN OPTION set.');
|
||||
}
|
||||
},
|
||||
},{
|
||||
},
|
||||
{
|
||||
id: 'rolmembers', label: gettext('Members'), type: 'collection', group: gettext('Membership'),
|
||||
mode: ['properties', 'edit', 'create'], cell: 'string',
|
||||
readonly: 'readonly',
|
||||
control: RoleMembersControl, model: pgBrowser.Node.Model.extend({
|
||||
keys: ['role'],
|
||||
idAttribute: 'role',
|
||||
defaults: {
|
||||
role: undefined,
|
||||
admin: false,
|
||||
},
|
||||
validate: function() {
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
filter: function(d) {
|
||||
return this.model.isNew() || (this.model.get('rolname') != d.label);
|
||||
},
|
||||
helpMessage: function(m) {
|
||||
if (m.has('read_only') && m.get('read_only') == false) {
|
||||
return gettext('Select the checkbox for roles to include WITH ADMIN OPTION.');
|
||||
} else {
|
||||
return gettext('Roles shown with a check mark have the WITH ADMIN OPTION set.');
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'variables', label: '', type: 'collection',
|
||||
group: gettext('Parameters'), hasDatabase: true, url: 'variables',
|
||||
model: pgBrowser.Node.VariableModel.extend({keys:['name', 'database']}),
|
||||
|
@ -9,6 +9,15 @@ SELECT
|
||||
LEFT JOIN pg_catalog.pg_roles rm ON (rm.oid = am.roleid)
|
||||
) rolmembership,
|
||||
(SELECT pg_catalog.array_agg(provider || '=' || label) FROM pg_catalog.pg_shseclabel sl1 WHERE sl1.objoid=r.oid) AS seclabels
|
||||
{% if rid %}
|
||||
,ARRAY(
|
||||
SELECT
|
||||
CASE WHEN pg.admin_option THEN '1' ELSE '0' END || pg.usename
|
||||
FROM
|
||||
(SELECT pg_roles.rolname AS usename, pg_auth_members.admin_option AS admin_option FROM pg_roles
|
||||
JOIN pg_auth_members ON pg_roles.oid=pg_auth_members.member AND pg_auth_members.roleid={{ rid|qtLiteral }}::oid) pg
|
||||
) rolmembers
|
||||
{% endif %}
|
||||
FROM
|
||||
pg_catalog.pg_roles r
|
||||
{% if rid %}
|
||||
|
@ -124,3 +124,15 @@ GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(rolname) }}
|
||||
|
||||
COMMENT ON ROLE {{ conn|qtIdent(rolname) }} IS {{ data.description|qtLiteral }};
|
||||
{% endif %}
|
||||
|
||||
{% if 'rol_revoked_admins' in data and
|
||||
data.rol_revoked_admins|length > 0
|
||||
%}
|
||||
|
||||
REVOKE ADMIN OPTION FOR {{ conn|qtIdent(rolname) }} FROM {{ conn|qtIdent(data.rol_revoked_admins)|join(', ') }};{% endif %}{% if 'rol_revoked' in data and data.rol_revoked|length > 0 %}
|
||||
|
||||
REVOKE {{ conn|qtIdent(rolname) }} FROM {{ conn|qtIdent(data.rol_revoked)|join(', ') }};{% endif %}{% if data.rol_admins and data.rol_admins|length > 0 %}
|
||||
|
||||
GRANT {{ conn|qtIdent(rolname) }} TO {{ conn|qtIdent(data.rol_admins)|join(', ') }} WITH ADMIN OPTION;{% endif %}{% if data.rol_members and data.rol_members|length > 0 %}
|
||||
|
||||
GRANT {{ conn|qtIdent(rolname) }} TO {{ conn|qtIdent(data.rol_members)|join(', ') }};{% endif %}
|
||||
|
@ -10,6 +10,15 @@ SELECT
|
||||
ORDER BY rm.rolname
|
||||
) AS rolmembership,
|
||||
(SELECT pg_catalog.array_agg(provider || '=' || label) FROM pg_catalog.pg_shseclabel sl1 WHERE sl1.objoid=r.oid) AS seclabels
|
||||
{% if rid %}
|
||||
,ARRAY(
|
||||
SELECT
|
||||
CASE WHEN pg.admin_option THEN '1' ELSE '0' END || pg.usename
|
||||
FROM
|
||||
(SELECT pg_roles.rolname AS usename, pg_auth_members.admin_option AS admin_option FROM pg_roles
|
||||
JOIN pg_auth_members ON pg_roles.oid=pg_auth_members.member AND pg_auth_members.roleid={{ rid|qtLiteral }}::oid) pg
|
||||
) rolmembers
|
||||
{% endif %}
|
||||
FROM
|
||||
pg_catalog.pg_roles r
|
||||
{% if rid %}
|
||||
|
@ -99,3 +99,15 @@ GRANT {{ conn|qtIdent(data.members)|join(', ') }} TO {{ conn|qtIdent(rolname) }}
|
||||
|
||||
COMMENT ON ROLE {{ conn|qtIdent(rolname) }} IS {{ data.description|qtLiteral }};
|
||||
{% endif %}
|
||||
|
||||
{% if 'rol_revoked_admins' in data and
|
||||
data.rol_revoked_admins|length > 0
|
||||
%}
|
||||
|
||||
REVOKE ADMIN OPTION FOR {{ conn|qtIdent(rolname) }} FROM {{ conn|qtIdent(data.rol_revoked_admins)|join(', ') }};{% endif %}{% if 'rol_revoked' in data and data.rol_revoked|length > 0 %}
|
||||
|
||||
REVOKE {{ conn|qtIdent(rolname) }} FROM {{ conn|qtIdent(data.rol_revoked)|join(', ') }};{% endif %}{% if data.rol_admins and data.rol_admins|length > 0 %}
|
||||
|
||||
GRANT {{ conn|qtIdent(rolname) }} TO {{ conn|qtIdent(data.rol_admins)|join(', ') }} WITH ADMIN OPTION;{% endif %}{% if data.rol_members and data.rol_members|length > 0 %}
|
||||
|
||||
GRANT {{ conn|qtIdent(rolname) }} TO {{ conn|qtIdent(data.rol_members)|join(', ') }};{% endif %}
|
||||
|
@ -117,7 +117,8 @@ select[readonly].select2-hidden-accessible + .select2-container {
|
||||
}
|
||||
|
||||
select[readonly].select2-hidden-accessible + .select2-container .select2-selection {
|
||||
background: #eee;
|
||||
background: $select2-readonly;
|
||||
color: $text-muted;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
@ -369,3 +369,5 @@ $erd-link-selected-color: $color-fg !default;
|
||||
@return '%23' + str-slice('#{$colour}', 2, -1)
|
||||
}
|
||||
$erd-bg-grid: url("data:image/svg+xml, %3Csvg width='100%25' viewBox='0 0 45 45' style='background-color:#{url-friendly-colour($erd-canvas-bg)}' height='100%25' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cpattern id='smallGrid' width='15' height='15' patternUnits='userSpaceOnUse'%3E%3Cpath d='M 15 0 L 0 0 0 15' fill='none' stroke='#{url-friendly-colour($erd-canvas-grid)}' stroke-width='0.5'/%3E%3C/pattern%3E%3Cpattern id='grid' width='45' height='45' patternUnits='userSpaceOnUse'%3E%3Crect width='100' height='100' fill='url(%23smallGrid)'/%3E%3Cpath d='M 100 0 L 0 0 0 100' fill='none' stroke='#{url-friendly-colour($erd-canvas-grid)}' stroke-width='1'/%3E%3C/pattern%3E%3C/defs%3E%3Crect width='100%25' height='100%25' fill='url(%23grid)' /%3E%3C/svg%3E%0A");
|
||||
|
||||
$select2-readonly: $color-gray-lighter !default;
|
||||
|
@ -134,3 +134,5 @@ $erd-canvas-bg: $color-gray-light;
|
||||
$erd-canvas-grid: #444952;
|
||||
$erd-link-color: $color-fg;
|
||||
$erd-link-selected-color: $color-fg;
|
||||
|
||||
$select2-readonly: $color-bg;
|
||||
|
@ -205,3 +205,5 @@ $span-text-color: #9D9FA1 !default;
|
||||
$span-text-color-hover: $black !default;
|
||||
$quick-search-a-text-color: $black !default;
|
||||
$quick-search-info-icon: #8A8A8A !default;
|
||||
|
||||
$select2-readonly: $color-gray;
|
||||
|
Loading…
Reference in New Issue
Block a user