From 696cb0fa059003eebf71a432ebc0e789472d35fb Mon Sep 17 00:00:00 2001 From: Akshay Joshi Date: Fri, 10 Feb 2023 10:26:01 +0530 Subject: [PATCH] Ensure that Grant column permission to a view is visible in the SQL tab. #5685 --- docs/en_US/release_notes.rst | 1 + docs/en_US/release_notes_6_21.rst | 31 +++++ .../databases/schemas/views/__init__.py | 123 +++++++++--------- .../templates/views/pg/default/sql/grant.sql | 4 +- 4 files changed, 94 insertions(+), 65 deletions(-) create mode 100644 docs/en_US/release_notes_6_21.rst diff --git a/docs/en_US/release_notes.rst b/docs/en_US/release_notes.rst index 90e0f6126..b6a9e1f53 100644 --- a/docs/en_US/release_notes.rst +++ b/docs/en_US/release_notes.rst @@ -11,6 +11,7 @@ notes for it. .. toctree:: :maxdepth: 1 + release_notes_6_21 release_notes_6_20 release_notes_6_19 release_notes_6_18 diff --git a/docs/en_US/release_notes_6_21.rst b/docs/en_US/release_notes_6_21.rst new file mode 100644 index 000000000..d8717483c --- /dev/null +++ b/docs/en_US/release_notes_6_21.rst @@ -0,0 +1,31 @@ +************ +Version 6.21 +************ + +Release date: 2023-03-09 + +This release contains a number of bug fixes and new features since the release of pgAdmin 4 v6.20. + +Supported Database Servers +************************** +**PostgreSQL**: 10, 11, 12, 13, 14 and 15 + +**EDB Advanced Server**: 10, 11, 12, 13, 14 and 15 + +Bundled PostgreSQL Utilities +**************************** +**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 15.1 + + +New features +************ + + +Housekeeping +************ + + +Bug fixes +********* + + | `Issue #5685 `_ - Ensure that Grant column permission to a view is visible in the SQL tab. diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py index e1986d147..465f0ffc6 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py @@ -252,6 +252,14 @@ def check_precondition(f): self.column_template_path = 'columns/sql/#{0}#'.format( self.manager.version) + try: + self.allowed_acls = render_template( + "/".join([self.template_path, self._ALLOWED_PRIVS_JSON]) + ) + self.allowed_acls = json.loads(self.allowed_acls, encoding='utf-8') + except Exception as e: + current_app.logger.exception(e) + return f(*args, **kwargs) return wrap @@ -370,6 +378,7 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare): self.conn = None self.template_path = None self.template_initial = 'views' + self.allowed_acls = [] @staticmethod def ppas_template_path(ver): @@ -793,22 +802,15 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare): ViewNode._get_info_from_data(data, res) - try: - acls = render_template( - "/".join([self.template_path, self._ALLOWED_PRIVS_JSON]) - ) - acls = json.loads(acls, encoding='utf-8') - except Exception as e: - current_app.logger.exception(e) - # Privileges - ViewNode._parse_privilege_data(acls, data) + ViewNode._parse_privilege_data(self.allowed_acls, data) data['del_sql'] = False old_data['acl_sql'] = '' is_error, errmsg = self._get_definition_data(vid, data, old_data, - res, acls) + res, + self.allowed_acls) if is_error: return None, errmsg @@ -866,17 +868,8 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare): if 'schema' in data and isinstance(data['schema'], int): data['schema'] = self._get_schema(data['schema']) - acls = [] - try: - acls = render_template( - "/".join([self.template_path, self._ALLOWED_PRIVS_JSON]) - ) - acls = json.loads(acls, encoding='utf-8') - except Exception as e: - current_app.logger.exception(e) - # Privileges - ViewNode._parse_priv_data(acls, data) + ViewNode._parse_priv_data(self.allowed_acls, data) sql = render_template("/".join( [self.template_path, self._SQL_PREFIX + self._CREATE_SQL]), @@ -1361,6 +1354,43 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare): self._UPDATE_SQL.format(self.manager.version)]), o_data=o_data, data=res, is_view_only=True) sql_data += SQL + + # Get Column Grant SQL + for rows in data['rows']: + res = { + 'name': rows['name'], + 'table': rows['relname'], + 'schema': self.view_schema + } + + if 'attacl' in rows and rows['attacl']: + # We need to parse & convert ACL coming from database to json + # format + SQL = render_template( + "/".join([self.column_template_path, 'acl.sql']), + tid=vid, clid=rows['attnum']) + status, acl = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=acl) + + allowed_acls = [] + if self.allowed_acls and 'datacl' in self.allowed_acls and \ + 'acl' in self.allowed_acls['datacl']: + allowed_acls = self.allowed_acls['datacl']['acl'] + + for row in acl['rows']: + priv = parse_priv_from_db(row) + res.setdefault(row['deftype'], []).append(priv) + res[row['deftype']] = \ + parse_priv_to_db(res[row['deftype']], allowed_acls) + + grant_sql = render_template("/".join( + [self.template_path, self._SQL_PREFIX + self._GRANT_SQL]), + data=res) + + sql_data += grant_sql + return sql_data @check_precondition @@ -1414,19 +1444,10 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare): result.update(res['rows'][0]) - acls = [] - try: - acls = render_template( - "/".join([self.template_path, self._ALLOWED_PRIVS_JSON]) - ) - acls = json.loads(acls, encoding='utf-8') - except Exception as e: - current_app.logger.exception(e) - # Privileges - for aclcol in acls: + for aclcol in self.allowed_acls: if aclcol in result: - allowedacl = acls[aclcol] + allowedacl = self.allowed_acls[aclcol] result[aclcol] = parse_priv_to_db( result[aclcol], allowedacl['acl'] ) @@ -1766,6 +1787,7 @@ class MViewNode(ViewNode, VacuumSettings): super().__init__(*args, **kwargs) self.template_initial = 'mviews' + self.allowed_acls = [] @staticmethod def ppas_template_path(ver): @@ -1834,19 +1856,10 @@ class MViewNode(ViewNode, VacuumSettings): # table vacuum toast: separate list of changed and reset data for self.merge_to_vacuum_data(old_data, data, 'vacuum_toast') - acls = [] - try: - acls = render_template( - "/".join([self.template_path, self._ALLOWED_PRIVS_JSON]) - ) - acls = json.loads(acls, encoding='utf-8') - except Exception as e: - current_app.logger.exception(e) - # Privileges - for aclcol in acls: + for aclcol in self.allowed_acls: if aclcol in data: - allowedacl = acls[aclcol] + allowedacl = self.allowed_acls[aclcol] for key in ['added', 'changed', 'deleted']: if key in data[aclcol]: @@ -1902,19 +1915,10 @@ class MViewNode(ViewNode, VacuumSettings): if data.get('toast_autovacuum', False): data['vacuum_data'] += vacuum_toast - acls = [] - try: - acls = render_template( - "/".join([self.template_path, self._ALLOWED_PRIVS_JSON]) - ) - acls = json.loads(acls, encoding='utf-8') - except Exception as e: - current_app.logger.exception(e) - # Privileges - for aclcol in acls: + for aclcol in self.allowed_acls: if aclcol in data: - allowedacl = acls[aclcol] + allowedacl = self.allowed_acls[aclcol] data[aclcol] = parse_priv_to_db( data[aclcol], allowedacl['acl'] ) @@ -1970,19 +1974,10 @@ class MViewNode(ViewNode, VacuumSettings): result['vacuum_data'] = vacuum_table + vacuum_toast - acls = [] - try: - acls = render_template( - "/".join([self.template_path, self._ALLOWED_PRIVS_JSON]) - ) - acls = json.loads(acls, encoding='utf-8') - except Exception as e: - current_app.logger.exception(e) - # Privileges - for aclcol in acls: + for aclcol in self.allowed_acls: if aclcol in result: - allowedacl = acls[aclcol] + allowedacl = self.allowed_acls[aclcol] result[aclcol] = parse_priv_to_db( result[aclcol], allowedacl['acl'] ) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/pg/default/sql/grant.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/pg/default/sql/grant.sql index a7e1585bd..05702c8c3 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/pg/default/sql/grant.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/templates/views/pg/default/sql/grant.sql @@ -1,6 +1,8 @@ {# ===== Grant Permissions to User Role on Views/Tables ===== #} {% import 'macros/schemas/security.macros' as SECLABEL %} {% import 'macros/schemas/privilege.macros' as PRIVILEGE %} +{% import 'columns/macros/privilege.macros' as COLUMN_PRIVILEGE %} {# ===== We will generate Security Label SQL using macro ===== #} {% if data.seclabels %}{% for r in data.seclabels %}{{ SECLABEL.SET(conn, 'VIEW', data.name, r.provider, r.label, data.schema) }}{{'\r'}}{% endfor %}{{'\r'}}{% endif %}{% if data.datacl %} -{% for priv in data.datacl %}{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }}{% endfor %}{% endif %} +{% for priv in data.datacl %}{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }}{% endfor %}{% endif %}{% if data.attacl %} +{% for priv in data.attacl %}{{ COLUMN_PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }}{% endfor %}{% endif %}