diff --git a/docs/en_US/release_notes_4_23.rst b/docs/en_US/release_notes_4_23.rst index c37160722..7093819d4 100644 --- a/docs/en_US/release_notes_4_23.rst +++ b/docs/en_US/release_notes_4_23.rst @@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o New features ************ +| `Issue #5516 `_ - Added support of Row Security Policies. | `Issue #5576 `_ - Improve error messaging if the storage and log directories cannot be created. Housekeeping diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py index 30d51e822..13742a619 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py @@ -665,6 +665,17 @@ class TableView(BaseTableView, DataTypeReader, VacuumSettings, table_row_count_threshold = table_row_count_pref.get() estimated_row_count = int(res['rows'][0].get('reltuples', 0)) + # Check whether 'rlspolicy' in response as it supported for + # version 9.5 and above + if 'rlspolicy' in res['rows'][0]: + # Set the value of rls policy + if res['rows'][0]['rlspolicy'] == "true": + res['rows'][0]['rlspolicy'] = True + + # Set the value of force rls policy for table owner + if res['rows'][0]['forcerlspolicy'] == "true": + res['rows'][0]['forcerlspolicy'] = True + # If estimated rows are greater than threshold then if estimated_row_count and \ estimated_row_count > table_row_count_threshold: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/__init__.py new file mode 100644 index 000000000..65dc046d3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/__init__.py @@ -0,0 +1,589 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Implements policy Node""" + +import simplejson as json +from functools import wraps + +import pgadmin.browser.server_groups.servers.databases as databases +from flask import render_template, request, jsonify +from flask_babelex import gettext +from pgadmin.browser.collection import CollectionNodeModule +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.utils.ajax import make_json_response, internal_server_error, \ + make_response as ajax_response, gone +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from pgadmin.browser.server_groups.servers.databases.schemas.tables. \ + row_security_policies import utils as row_security_policies_utils +from pgadmin.utils.compile_template_name import compile_template_path + + +class RowSecurityModule(CollectionNodeModule): + """ + class RowSecurityModule(CollectionNodeModule) + + A module class for policy node derived from CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the RowSecurityModule + and it's base module. + + * get_nodes(gid, sid, did) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overplidden from its base class to + make the node as leaf node. + + * script_load() + - Load the module script for policy, when any of the database node is + initialized. + """ + + NODE_TYPE = 'row_security_policy' + COLLECTION_LABEL = gettext('RLS Policies') + + def __init__(self, *args, **kwargs): + super(RowSecurityModule, self).__init__(*args, **kwargs) + self.min_gpdbver = 1000000000 + self.min_ver = 90500 + self.max_ver = None + + def get_nodes(self, gid, sid, did, scid, **kwargs): + """ + Generate the collection node + :param gid: group id + :param sid: server id + :param did: database id + :param scid: Schema ID + """ + yield self.generate_browser_collection_node(did) + + @property + def node_inode(self): + """ + Overplide the property to make the node as leaf node + """ + return False + + @property + def script_load(self): + """ + Load the module script for policy, when any of the database node is + initialized. + """ + return databases.DatabaseModule.NODE_TYPE + + @property + def module_use_template_javascript(self): + """ + Returns whether Jinja2 template is used for generating the javascript + module. + """ + return False + + +blueprint = RowSecurityModule(__name__) + + +class RowSecurityView(PGChildNodeView): + """ + class RowSecurityView(PGChildNodeView) + + A view class for policy node derived from PGChildNodeView. + This class is + responsible for all the stuff related to view like + create/update/delete policy, showing properties of policy node, + showing sql in sql pane. + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the RowSecurityView and it's base view. + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function is used to list all the policy nodes within that + collection. + + * nodes() + - This function will used to create all the child node within that + collection. Here it will create all the policy nodes. + + * properties(gid, sid, did, rg_id) + - This function will show the properties of the selected policy node + + * create(gid, sid, did, rg_id) + - This function will create the new policy object + + * update(gid, sid, did, rg_id) + - This function will update the data for the selected policy node + + * delete(self, gid, sid, rg_id): + - This function will drop the policy object + + * msql(gid, sid, did, rg_id) + - This function is used to return modified sql for the selected + policy node + + * get_sql(data, rg_id) + - This function will generate sql from model data + + * sql(gid, sid, did, rg_id): + - This function will generate sql to show in sql pane for the selected + policy node. + """ + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [ + {'type': 'int', 'id': 'plid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create', 'delete': 'delete'} + ], + 'delete': [{'delete': 'delete'}, {'delete': 'delete'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}] + }) + + def _init_(self, **kwargs): + self.conn = None + self.template_path = None + self.manager = None + super(RowSecurityView, self).__init__(**kwargs) + + def check_precondition(f): + """ + This function will behave as a decorator which will check the + database connection before running view. It will also attach + manager, conn & template_path properties to self + """ + + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver( + PG_DEFAULT_DRIVER + ).connection_manager(kwargs['sid']) + self.conn = self.manager.connection(did=kwargs['did']) + schema, table = row_security_policies_utils.get_parent(self.conn, + kwargs[ + 'tid']) + self.datlastsysoid = self.manager.db_info[ + kwargs['did'] + ]['datlastsysoid'] if self.manager.db_info is not None and \ + kwargs['did'] in self.manager.db_info else 0 + self.schema = schema + self.table = table + # Set template path for the sql scripts + self.table_template_path = compile_template_path( + 'tables/sql', + self.manager.server_type, + self.manager.version + ) + self.template_path = 'row_security_policies/sql/#{0}#'.format( + self.manager.version) + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid, tid): + """ + Fetch all policy properties and render into properties tab + """ + + # fetch schema name by schema id + sql = render_template("/".join( + [self.template_path, 'properties.sql']), tid=tid) + status, res = self.conn.execute_dict(sql) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def node(self, gid, sid, did, scid, tid, plid): + """ + return single node + """ + sql = render_template("/".join( + [self.template_path, 'nodes.sql']), plid=plid) + + status, rset = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + + if len(rset['rows']) == 0: + return gone(gettext("""Could not find the policy in the table.""")) + + res = self.blueprint.generate_browser_node( + rset['rows'][0]['oid'], + tid, + rset['rows'][0]['name'], + icon="icon-row_security_policy" + ) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, tid): + """ + List all the policies under the policies Collection node + """ + res = [] + sql = render_template("/".join( + [self.template_path, 'nodes.sql']), tid=tid) + + status, rset = self.conn.execute_2darray(sql) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-row_security_policy" + )) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def properties(self, gid, sid, did, scid, tid, plid): + """ + Fetch the properties of an individual policy and render in + properties tab + + """ + status, data = self._fetch_properties(plid) + if not status: + return data + + return ajax_response( + response=data, + status=200 + ) + + def _fetch_properties(self, plid): + """ + This function is used to fetch the properties of the specified object + :param plid: + :return: + """ + sql = render_template("/".join( + [self.template_path, 'properties.sql'] + ), plid=plid, datlastsysoid=self.datlastsysoid) + status, res = self.conn.execute_dict(sql) + + if not status: + return False, internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return False, gone( + gettext("""Could not find the policy in the table.""")) + + data = dict(res['rows'][0]) + + res = data + + return True, res + + @check_precondition + def create(self, gid, sid, did, scid, tid): + """ + This function will creates new the policy object + :param did: database id + :param sid: server id + :param gid: group id + :param tid: table id + :param scid: Schema ID + :return: + """ + + required_args = [ + 'name', + ] + + data = request.form if request.form else json.loads( + request.data, encoding='utf-8' + ) + data['schema'] = self.schema + data['table'] = self.table + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Could not find the required parameter ({})." + ).format(arg) + ) + try: + sql = render_template("/".join([self.template_path, 'create.sql']), + data=data, + conn=self.conn, + ) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + # we need oid to to add object in tree at browser + sql = render_template( + "/".join([self.template_path, 'get_position.sql']), + tid=tid, data=data + ) + status, plid = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=tid) + return jsonify( + node=self.blueprint.generate_browser_node( + plid, + tid, + data['name'], + icon="icon-row_security_policy" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, scid, did, tid, plid=None): + """ + This function will update policy object + :param plid: policy id + :param did: database id + :param sid: server id + :param gid: group id + :param tid: table id + :param scid: Schema ID + :return: + """ + data = request.form if request.form else json.loads( + request.data, encoding='utf-8' + ) + try: + sql, name = row_security_policies_utils.get_sql(self.conn, data, + did, + tid, plid, + self.datlastsysoid, + self.schema, + self.table) + # Most probably this is due to error + if not isinstance(sql, str): + return sql + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + return jsonify( + node=self.blueprint.generate_browser_node( + plid, + tid, + name, + icon="icon-row_security_policy" + ) + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, plid=None): + """ + This function will drop the policy object + :param plid: policy id + :param did: database id + :param sid: server id + :param gid: group id + :param tid: table id + :param scid: Schema ID + :return: + """ + # Below will deplide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + + if plid is None: + data = request.form if request.form else json.loads( + request.data, encoding='utf-8' + ) + else: + data = {'ids': [plid]} + + for plid in data['ids']: + try: + # Get name for policy from plid + sql = render_template("/".join([self.template_path, + 'get_policy_name.sql']), + plid=plid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + + if not res['rows']: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + 'Error: Object not found.' + ), + info=gettext( + 'The specified policy object could not be found.\n' + ) + ) + + # drop policy + result = res['rows'][0] + result['schema'] = self.schema + result['table'] = self.table + sql = render_template("/".join([self.template_path, + 'delete.sql']), + policy_name=result['name'], + cascade=cascade, + result=result + ) + status, res = self.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + return make_json_response( + success=1, + info=gettext("policy dropped") + ) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, plid=None): + """ + This function returns modified sql + """ + data = dict(request.args) + + sql, name = row_security_policies_utils.get_sql(self.conn, data, did, + tid, plid, + self.datlastsysoid, + self.schema, + self.table) + if not isinstance(sql, str): + return sql + sql = sql.strip('\n').strip(' ') + + if sql == '': + sql = "--modified sql" + return make_json_response( + data=sql, + status=200 + ) + + @check_precondition + def sql(self, gid, sid, did, scid, tid, plid): + """ + This function will generate sql to render into the sql panel + """ + sql = render_template("/".join( + [self.template_path, 'properties.sql']), plid=plid) + status, res = self.conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + if len(res['rows']) == 0: + return gone(gettext("""Could not find the policy in the table.""")) + res = dict(res['rows'][0]) + res_data = res + res_data['schema'] = self.schema + res_data['table'] = self.table + + sql = render_template("/".join( + [self.template_path, 'create.sql']), + data=res_data, display_comments=True) + + return ajax_response(response=sql) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, plid): + """ + This function gets the dependents and returns an ajax response + for the policy node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + plid: policy ID + tid: table id + scid: Schema ID + """ + dependents_result = self.get_dependents(self.conn, plid) + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, plid): + """ + This function gets the dependencies and returns an ajax response + for the policy node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + plid: policy ID + tid: table id + scid: Schema ID + """ + dependencies_result = self.get_dependencies(self.conn, plid) + return ajax_response( + response=dependencies_result, + status=200 + ) + + +RowSecurityView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/img/coll-row_security_policy.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/img/coll-row_security_policy.svg new file mode 100644 index 000000000..9fbad7569 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/img/coll-row_security_policy.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/img/row_security_policy.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/img/row_security_policy.svg new file mode 100644 index 000000000..e72c7845e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/img/row_security_policy.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/js/row_security_policy.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/js/row_security_policy.js new file mode 100644 index 000000000..9f1a9d582 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/js/row_security_policy.js @@ -0,0 +1,184 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2020, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +define('pgadmin.node.row_security_policy', [ + 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', + 'sources/pgadmin', 'pgadmin.browser', + 'pgadmin.backform', 'pgadmin.alertifyjs', + 'pgadmin.node.schema.dir/schema_child_tree_node', + 'pgadmin.browser.collection', +], function( + gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, alertify, + SchemaChildTreeNode +) { + + if (!pgBrowser.Nodes['coll-row_security_policy']) { + pgAdmin.Browser.Nodes['coll-row_security_policy'] = + pgAdmin.Browser.Collection.extend({ + node: 'row_security_policy', + label: gettext('RLS Policies'), + type: 'coll-row_security_policy', + columns: ['name', 'description'], + canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + }); + } + + if (!pgBrowser.Nodes['row_security_policy']) { + pgAdmin.Browser.Nodes['row_security_policy'] = pgBrowser.Node.extend({ + parent_type: ['table', 'view', 'partition'], + collection_type: ['coll-table', 'coll-view'], + type: 'row_security_policy', + label: gettext('RLS Policy'), + hasSQL: true, + hasDepends: true, + width: pgBrowser.stdW.sm + 'px', + sqlAlterHelp: 'sql-alterpolicy.html', + sqlCreateHelp: 'sql-createpolicy.html', + dialogHelp: url_for('help.static', {'filename': 'row_security_policy_dialog.html'}), + url_jump_after_node: 'schema', + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_row_security_policy_on_coll', node: 'coll-row_security_policy', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 1, label: gettext('RLS Policy...'), + icon: 'wcTabIcon icon-row_security_policy', data: {action: 'create', check: true}, + enable: 'canCreate', + },{ + name: 'create_row_security_policy', node: 'row_security_policy', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 1, label: gettext('RLS Policy...'), + icon: 'wcTabIcon icon-row_security_policy', data: {action: 'create', check: true}, + enable: 'canCreate', + }, + { + name: 'create_row_security_policy_on_coll', node: 'table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 6, label: gettext('RLS Policy...'), + icon: 'wcTabIcon icon-row_security_policy', data: {action: 'create', check: true}, + enable: 'canCreate', + }, + ]); + }, + canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + model: pgAdmin.Browser.Node.Model.extend({ + idAttribute: 'oid', + defaults: { + name: undefined, + policyowner: 'public', + }, + schema: [{ + id: 'name', label: gettext('Name'), cell: 'string', + editable: true, type: 'text', readonly: false, cellHeaderClasses: 'width_percent_50', + },{ + id: 'oid', label: gettext('OID'), cell: 'string', + editable: false, type: 'text', mode: ['properties'], + }, + { + id: 'event', label: gettext('Event'), control: 'select2', deps:['event'], + group: gettext('Commands'), type: 'text',readonly: function(m) { + return !m.isNew();}, + select2: { + width: '100%', + allowClear: true, + }, + options:[ + {label: 'SELECT', value: 'SELECT'}, + {label: 'INSERT', value: 'INSERT'}, + {label: 'UPDATE', value: 'UPDATE'}, + {label: 'DELETE', value: 'DELETE'}, + ], + }, + { + id: 'using', label: gettext('Using'), deps: ['using', 'event'], + type: 'text', disabled: 'disableUsing', + mode: ['create', 'edit', 'properties'], + control: 'sql-field', visible: true, group: gettext('Commands'), + }, + { + id: 'withcheck', label: gettext('With Check'), deps: ['withcheck', 'event'], + type: 'text', mode: ['create', 'edit', 'properties'], + control: 'sql-field', visible: true, group: gettext('Commands'), + disabled: 'disableWithCheck', + }, + { + id: 'policyowner', label: gettext('Role'), cell: 'string', + control: 'node-list-by-name', + node: 'role', select2: { allowClear: false }, + mode: ['properties', 'edit','create'], + transform: function() { + var res = + Backform.NodeListByNameControl.prototype.defaults.transform.apply( + this, arguments + ); + res.unshift({ + label: 'public', value: 'public', + }); + return res; + }, + }], + validate: function(keys) { + var msg; + this.errorModel.clear(); + + // If nothing to validate + if (keys && keys.length == 0) { + return null; + } + + if(_.isUndefined(this.get('name')) + || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') { + msg = gettext('Name cannot be empty.'); + this.errorModel.set('name', msg); + return msg; + } + return null; + }, + disableWithCheck: function(m){ + var event = m.get('event'); + if ((event == 'SELECT') || (event == 'DELETE')){ + m.set('withcheck', ''); + return true; + } + return false; + }, + + disableUsing: function(m){ + var event = m.get('event'); + + if (event == 'INSERT'){ + return true; + } + return false; + }, + + }), + canCreate: function(itemData, item) { + + var treeData = this.getTreeNodeHierarchy(item), + server = treeData['server']; + + if (server && server.version < 90500) + return false; + + // by default we want to allow create menu + return true; + }, + }); + } + + return pgBrowser.Nodes['row_security_policy']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/__init__.py new file mode 100644 index 000000000..27cd89067 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/__init__.py @@ -0,0 +1,16 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from pgadmin.utils.route import BaseTestGenerator + + +class RulesTestGenerator(BaseTestGenerator): + + def runTest(self): + return [] diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/alter_policy.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/alter_policy.sql new file mode 100644 index 000000000..aa4c4093e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/alter_policy.sql @@ -0,0 +1,11 @@ + +-- POLICY: policy_1 ON public.test_emp_rule + +-- DROP POLICY policy_1 ON public.test_emp_rule; + +CREATE POLICY policy_1 + ON public.test_emp_rule + FOR ALL + TO public +; + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/alter_policy_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/alter_policy_msql.sql new file mode 100644 index 000000000..8614ff2c9 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/alter_policy_msql.sql @@ -0,0 +1,2 @@ +ALTER POLICY test ON public.test_emp_rule + RENAME TO policy_1; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_insert_policy.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_insert_policy.sql new file mode 100644 index 000000000..b130f3915 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_insert_policy.sql @@ -0,0 +1,11 @@ + +-- POLICY: insert_policy ON public.test_emp_rule + +-- DROP POLICY insert_policy ON public.test_emp_rule; + +CREATE POLICY insert_policy + ON public.test_emp_rule + FOR INSERT + TO public +; + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_public_policy.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_public_policy.sql new file mode 100644 index 000000000..6a3332e19 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_public_policy.sql @@ -0,0 +1,11 @@ + +-- POLICY: test ON public.test_emp_rule + +-- DROP POLICY test ON public.test_emp_rule; + +CREATE POLICY test + ON public.test_emp_rule + FOR ALL + TO public +; + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_select_policy.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_select_policy.sql new file mode 100644 index 000000000..d2f848e6b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/create_select_policy.sql @@ -0,0 +1,11 @@ + +-- POLICY: select_policy ON public.test_emp_rule + +-- DROP POLICY select_policy ON public.test_emp_rule; + +CREATE POLICY select_policy + ON public.test_emp_rule + FOR SELECT + TO public +; + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/test.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/test.json new file mode 100644 index 000000000..67525eb1b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/default/test.json @@ -0,0 +1,86 @@ +{ + "scenarios": [ + { + "type": "create", + "name": "Create Table For RLS policy", + "endpoint": "NODE-table.obj", + "sql_endpoint": "NODE-table.sql_id", + "data": { + "name": "test_emp_rule", + "columns": [ + { + "name": "emp_id", + "cltype": "integer", + "is_primary_key": true + }, + { + "name": "name", + "cltype": "text" + }, + { + "name": "salary", + "cltype": "bigint" + } + ], + "is_partitioned": false, + "schema": "public", + "spcname": "pg_default" + }, + "store_object_id": true + }, + { + "type": "create", + "name": "Create select RLS policy", + "endpoint": "NODE-row_security_policy.obj", + "sql_endpoint": "NODE-row_security_policy.sql_id", + "data": { + "name": "select_policy", + "event": "SELECT", + "policyowner": "public" + }, + "expected_sql_file": "create_select_policy.sql" + }, + { + "type": "create", + "name": "Create INSERT RLS policy", + "endpoint": "NODE-row_security_policy.obj", + "sql_endpoint": "NODE-row_security_policy.sql_id", + "data": { + "name": "insert_policy", + "event": "INSERT", + "policyowner": "public" + }, + "expected_sql_file": "create_insert_policy.sql" + }, + { + "type": "create", + "name": "Create RLS policy", + "endpoint": "NODE-row_security_policy.obj", + "sql_endpoint": "NODE-row_security_policy.sql_id", + "data": { + "name": "test" + }, + "expected_sql_file": "create_public_policy.sql" + }, + { + "type": "alter", + "name": "Alter policy name", + "endpoint": "NODE-row_security_policy.obj_id", + "sql_endpoint": "NODE-row_security_policy.sql_id", + "msql_endpoint": "NODE-row_security_policy.msql_id", + "data": { + "name": "policy_1" + }, + "expected_sql_file": "alter_policy.sql", + "expected_msql_file": "alter_policy_msql.sql" + }, + { + "type": "delete", + "name": "Drop policy", + "endpoint": "NODE-row_security_policy.delete_id", + "data": { + "name": "test_delete_policy_$%{}[]()&*^!@\"'`\\/#" + } + } + ] +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/rls_test_data.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/rls_test_data.json new file mode 100644 index 000000000..1adf62f44 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/rls_test_data.json @@ -0,0 +1,493 @@ +{ + "add_policy": [ + { + "name": "Add policy Node", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "test_data": { + "name": "PLACE_HOLDER", + "event": "INSERT" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Add owner specific policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "owner_policy": true, + "test_data": { + "name": "PLACE_HOLDER", + "policyowner": "PLACE_HOLDER", + "event": "SELECT" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Error while adding a policy using wrong table", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "wrong_table_id": true, + "test_data": { + "name": "PLACE_HOLDER", + "event": "Update" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 410 + } + }, + { + "name": "Error while adding a policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "error_creating_policy": true, + "test_data": { + "name": "PLACE_HOLDER" + }, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error ')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Error while while fetching the policy id using policy name", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "internal_server_error": true, + "test_data": { + "name": "PLACE_HOLDER" + }, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(True, True),(False, 'Mocked Internal Server Error ')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Exception while adding a policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "test_data": { + "name": "PLACE_HOLDER" + }, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error ')" + }, + "expected_data": { + "status_code": 500 + } + } + ], + "get_policy": [ + { + "name": "Get a policy URL", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Get a policy URL using wrong policy id", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "incorrect_policy_id": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 410 + } + }, + { + "name": "Error while fetching a policy properties", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Get a policies properties under table nodes", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "table_nodes": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Error while fetching a policies properties under table nodes", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "table_nodes": true, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Get a policy Node", + "url": "/browser/row_security_policy/nodes/", + "is_positive_test": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Get a policy Node using wrong policy id", + "url": "/browser/row_security_policy/nodes/", + "is_positive_test": true, + "incorrect_policy_id": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 410 + } + }, + { + "name": "Get a policy Node dependants", + "url": "/browser/row_security_policy/dependent/", + "is_positive_test": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Get a policy Node dependency", + "url": "/browser/row_security_policy/dependency/", + "is_positive_test": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Error while fetching the policies under the table nodes using wrong table id", + "url": "/browser/row_security_policy/nodes/", + "is_positive_test": false, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_2darray", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Get all the policies under the table nodes", + "url": "/browser/row_security_policy/nodes/", + "is_positive_test": true, + "mocking_required": false, + "table_nodes": true, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Get all the policies under the table nodes using wrong table id", + "url": "/browser/row_security_policy/nodes/", + "is_positive_test": true, + "incorrect_table_id": true, + "table_nodes": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Error while fetching all the policies under the table nodes using wrong table id", + "url": "/browser/row_security_policy/nodes/", + "is_positive_test": false, + "table_nodes": true, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_2darray", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Error while fetching a policy SQL", + "url": "/browser/row_security_policy/sql/", + "is_positive_test": false, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Get a policy SQL using wrong policy id", + "url": "/browser/row_security_policy/sql/", + "is_positive_test": true, + "incorrect_policy_id": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 410 + } + }, + { + "name": "Fetch msql of policy using wrong policy id", + "url": "/browser/row_security_policy/msql/", + "is_positive_test": false, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.browser.server_groups.servers.databases.schemas.tables.row_security_policies.utils.get_sql", + "return_value": "('', 'Mocked response')" + }, + "expected_data": { + "status_code": 200 + } + } + ], + "delete_policy": [ + { + "name": "Delete a policy URL", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "Error while fetching a policy to delete", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Error while deleting the policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Error while fetching a policy to delete", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "mocking_required": true, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(True, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "policy not found while deleting a policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "invalid_policy_id": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 410 + } + } + ], + "update_policy": [ + { + "name": "update a policy name", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + { + "name": "update a policy owner", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "owner_policy": true, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER", + "policyowner": "PLACEHOLDER" + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + + { + "name": "update a policy using clause", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER", + "using": true + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + + { + "name": "update a policy with check clause", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER", + "withcheck": true + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + }, + + { + "name": "Error while fetching a policy to update", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "mocking_required": true, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Error while updating the policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "mocking_required": true, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mock_data": { + "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar", + "return_value": "(False, 'Mocked Internal Server Error')" + }, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Error while fetching a policy to update using wrong policy id", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "wrong_policy_id": true, + "mocking_required": false, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mock_data": {}, + "expected_data": { + "status_code": 500 + } + }, + { + "name": "Error while updating the policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": false, + "mocking_required": true, + "test_data": { + "name": "PLACE_HOLDER", + "id": "PLACE_HOLDER" + }, + "mock_data": { + "function_name": "pgadmin.browser.server_groups.servers.databases.schemas.tables.row_security_policies.utils.get_sql", + "return_value": "('')" + }, + "expected_data": { + "status_code": 500 + } + } + ], + "delete_multiple_policy": [ + { + "name": "Delete multiple policy", + "url": "/browser/row_security_policy/obj/", + "is_positive_test": true, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } + } + ] +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_add.py new file mode 100644 index 000000000..2e7bf9b98 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_add.py @@ -0,0 +1,116 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid +import sys +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as policy_utils +from pgadmin.browser.server_groups.servers.roles.tests import \ + utils as roles_utils + +if sys.version_info < (3, 3): + from mock import patch +else: + from unittest.mock import patch + + +class RulesAddTestCase(BaseTestGenerator): + """This class will add new Policy under table node.""" + scenarios = utils.generate_scenarios('add_policy', + policy_utils.test_cases) + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a Policy.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to add a Policy.") + self.table_name = "table_for_policy_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + if hasattr(self, "owner_policy"): + self.role_name = "role_for_policy_%s" % str(uuid.uuid4())[1:8] + self.role_id = roles_utils.create_role(self.server, self.role_name) + + def runTest(self): + """This function will Policy under table node.""" + self.test_data['name'] = \ + "test_policy_add_%s" % (str(uuid.uuid4())[1:8]) + + if hasattr(self, "owner_policy"): + self.test_data['policyowner'] = self.role_name + + data = self.test_data + if self.is_positive_test: + response = self.create_policy(data) + else: + if hasattr(self, 'wrong_table_id'): + del data["name"] + response = self.create_policy(data) + elif hasattr(self, 'internal_server_error'): + with patch(self.mock_data["function_name"], + side_effect=eval(self.mock_data["return_value"])): + response = self.create_policy(data) + elif hasattr(self, 'error_creating_policy'): + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.create_policy(data) + else: + with patch(self.mock_data["function_name"], + side_effect=eval(self.mock_data["return_value"])): + response = self.create_policy(data) + self.assertEquals(response.status_code, + self.expected_data["status_code"]) + + def create_policy(self, data): + return self.tester.post( + "{0}{1}/{2}/{3}/{4}/{5}/".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id), + data=json.dumps(data), + content_type='html/json' + ) + + def tearDown(self): + connection = utils.get_db_connection(self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode']) + if hasattr(self, "owner_policy"): + policy_utils.delete_policy(self.server, self.db_name, + self.test_data['name'], + self.schema_name, + self.table_name) + roles_utils.delete_role(connection, self.role_name) + + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_delete.py new file mode 100644 index 000000000..459d9d624 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_delete.py @@ -0,0 +1,91 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as policy_utils +import sys + +if sys.version_info < (3, 3): + from mock import patch +else: + from unittest.mock import patch + + +class PolicyDeleteTestCase(BaseTestGenerator): + """This class will delete policy under table node.""" + scenarios = utils.generate_scenarios('delete_policy', + policy_utils.test_cases) + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to delete policy.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to delete policy.") + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.policy_name = "test_policy_delete_%s" % (str(uuid.uuid4())[1:8]) + self.policy_id = policy_utils.create_policy(self.server, self.db_name, + self.schema_name, + self.table_name, + self.policy_name) + + def delete_policy(self): + return self.tester.delete( + "{0}{1}/{2}/{3}/{4}/{5}/{6}".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id, + self.policy_id), + follow_redirects=True + ) + + def runTest(self): + """This function will delete policy under table node.""" + policy_response = policy_utils.verify_policy(self.server, self.db_name, + self.policy_name) + if not policy_response: + raise Exception("Could not find the policy to delete.") + + if self.is_positive_test: + if hasattr(self, "invalid_policy_id"): + self.policy_id = 9999 + response = self.delete_policy() + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.delete_policy() + + self.assertEquals(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_delete_multiple.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_delete_multiple.py new file mode 100644 index 000000000..d8d76ff6f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_delete_multiple.py @@ -0,0 +1,94 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid +import json + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as policy_utils + + +class PolicyDeleteTestCases(BaseTestGenerator): + """This class will delete policy under table node.""" + + scenarios = utils.generate_scenarios('delete_multiple_policy', + policy_utils.test_cases) + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to delete policy.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to delete policy.") + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.policy_name = "test_policy_delete_%s" % (str(uuid.uuid4())[1:8]) + self.policy_name_1 = "test_policy_delete_%s" % (str(uuid.uuid4())[1:8]) + self.rule_ids = [policy_utils.create_policy(self.server, self.db_name, + self.schema_name, + self.table_name, + self.policy_name), + policy_utils.create_policy(self.server, self.db_name, + self.schema_name, + self.table_name, + self.policy_name_1), + ] + + def delete_multiple_policy(self, data): + return self.tester.delete( + "{0}{1}/{2}/{3}/{4}/{5}/".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id + ), + follow_redirects=True, + data=json.dumps(data), + content_type='html/json' + ) + + def runTest(self): + """This function will delete policy under table node.""" + rule_response = policy_utils.verify_policy(self.server, self.db_name, + self.policy_name) + if not rule_response: + raise Exception("Could not find the policy to delete.") + + rule_response = policy_utils.verify_policy(self.server, self.db_name, + self.policy_name_1) + if not rule_response: + raise Exception("Could not find the policy to delete.") + + data = {'ids': self.rule_ids} + if self.is_positive_test: + response = self.delete_multiple_policy(data) + self.assertEquals(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_get.py new file mode 100644 index 000000000..f34c4425a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_get.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as policy_utils + +import sys + +if sys.version_info < (3, 3): + from mock import patch +else: + from unittest.mock import patch + + +class PolicyGetTestCase(BaseTestGenerator): + """This class will fetch the Policy under table node.""" + scenarios = utils.generate_scenarios('get_policy', + policy_utils.test_cases) + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to delete Policy.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to delete Policy.") + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.policy_name = "test_policy_delete_%s" % (str(uuid.uuid4())[1:8]) + self.policy_id = policy_utils.create_policy(self.server, self.db_name, + self.schema_name, + self.table_name, + self.policy_name) + + def get_policy(self): + return self.tester.get( + "{0}{1}/{2}/{3}/{4}/{5}/{6}".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id, + self.policy_id), + follow_redirects=True + ) + + def runTest(self): + """This function will fetch the Policy under table node.""" + + if self.is_positive_test: + if hasattr(self, "incorrect_policy_id"): + self.policy_id = 9999 + if hasattr(self, "table_nodes"): + self.policy_id = '' + response = self.get_policy() + else: + response = self.get_policy() + else: + if hasattr(self, "table_nodes"): + self.policy_id = '' + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.get_policy() + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + response = self.get_policy() + + self.assertEquals(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_put.py new file mode 100644 index 000000000..22c6124e5 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/test_rls_put.py @@ -0,0 +1,122 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as policy_utils +from pgadmin.browser.server_groups.servers.roles.tests import \ + utils as roles_utils +import sys + +if sys.version_info < (3, 3): + from mock import patch +else: + from unittest.mock import patch + + +class PolicyUpdateTestCase(BaseTestGenerator): + """This class will update the policy under table node.""" + scenarios = utils.generate_scenarios('update_policy', + policy_utils.test_cases) + + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to delete policy.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to delete policy.") + self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + self.policy_name = "test_policy_delete_%s" % (str(uuid.uuid4())[1:8]) + self.policy_id = policy_utils.create_policy(self.server, self.db_name, + self.schema_name, + self.table_name, + self.policy_name) + if hasattr(self, "owner_policy"): + self.role_name = "role_for_policy_%s" % \ + str(uuid.uuid4())[1:8] + self.role_id = roles_utils.create_role(self.server, self.role_name) + + def update_policy(self, data): + return self.tester.put( + "{0}{1}/{2}/{3}/{4}/{5}/{6}".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id, + self.policy_id), + data=json.dumps(data), + follow_redirects=True) + + def runTest(self): + """This function will update the policy under table node.""" + policy_name = policy_utils.verify_policy(self.server, self.db_name, + self.policy_name) + self.test_data['name'] = "test_policy_update_%s" % ( + str(uuid.uuid4())[1:8]) + self.test_data['id'] = self.policy_id + + if hasattr(self, 'owner_policy'): + self.test_data['policyowner'] = self.role_name + + if not policy_name: + raise Exception("Could not find the policy to update.") + + if self.is_positive_test: + if hasattr(self, "wrong_policy_id"): + self.policy_id = 9999 + if hasattr(self, "plid_none"): + self.policy_id = '' + response = self.update_policy(self.test_data) + else: + with patch(self.mock_data["function_name"], + return_value=eval(self.mock_data["return_value"])): + if hasattr(self, "wrong_policy_id"): + self.policy_id = 9999 + response = self.update_policy(self.test_data) + + self.assertEquals(response.status_code, + self.expected_data["status_code"]) + + def tearDown(self): + connection = utils.get_db_connection(self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode']) + + if hasattr(self, "owner_policy"): + policy_utils.delete_policy(self.server, self.db_name, + self.test_data['name'], + self.schema_name, + self.table_name) + roles_utils.delete_role(connection, self.role_name) + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/utils.py new file mode 100644 index 000000000..531a57a84 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/tests/utils.py @@ -0,0 +1,140 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from __future__ import print_function + +import sys +import os +import json +import traceback + +from regression.python_test_utils import test_utils as utils + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +with open(CURRENT_PATH + "/rls_test_data.json") as data_file: + test_cases = json.load(data_file) + + +def create_policy(server, db_name, schema_name, table_name, policy_name): + """ + This function creates a policy under provided table. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param schema_name: schema name + :type schema_name: str + :param table_name: table name + :type table_name: str + :param policy_name: policy name + :type policy_name: str + :return policy_id: policy id + :rtype: int + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + old_isolation_level = connection.isolation_level + connection.set_isolation_level(0) + pg_cursor = connection.cursor() + query = "CREATE policy %s on %s.%s To public" % \ + (policy_name, schema_name, table_name) + pg_cursor.execute(query) + connection.set_isolation_level(old_isolation_level) + connection.commit() + # Get role oid of newly added policy + pg_cursor.execute("select oid from pg_policy where polname='%s'" % + policy_name) + policy = pg_cursor.fetchone() + policy_id = '' + if policy: + policy_id = policy[0] + connection.close() + return policy_id + except Exception: + traceback.print_exc(file=sys.stderr) + raise + + +def verify_policy(server, db_name, policy_name): + """ + This function verifies policy exist in database or not. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param policy_name: policy name + :type policy_name: str + :return policy: policy record from database + :rtype: tuple + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute("select * from pg_policy where polname='%s'" % + policy_name) + policy = pg_cursor.fetchone() + connection.close() + return policy + except Exception: + traceback.print_exc(file=sys.stderr) + raise + + +def delete_policy(server, db_name, policy_name, schema_name, table_name): + """ + This function use to delete the existing roles in the servers + + :param db_name: db_name + :type db_name: db_name object + :param server: server + :type server: server object + :param policy_name: policy name + :type policy_name: str + :param schema_name: schema name + :type schema_name: str + :param table_name: table name + :type table_name: str + :return: None + """ + + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + + pg_cursor.execute("select * from pg_policy where polname='%s'" % + policy_name) + policy_count = pg_cursor.fetchone() + if policy_count: + old_isolation_level = connection.isolation_level + connection.set_isolation_level(0) + pg_cursor = connection.cursor() + query = "DROP policy %s on %s.%s" % \ + (policy_name, schema_name, table_name) + pg_cursor.execute(query) + connection.set_isolation_level(old_isolation_level) + connection.commit() + connection.close() + except Exception: + traceback.print_exc(file=sys.stderr) + raise diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/utils.py new file mode 100644 index 000000000..f61eadca1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/utils.py @@ -0,0 +1,145 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Utility class for row level security. """ + +from flask import render_template +from flask_babelex import gettext as _ +from pgadmin.utils.ajax import internal_server_error +from pgadmin.utils.exception import ObjectGone +from functools import wraps + + +def get_template_path(f): + """ + This function will behave as a decorator which will prepare + the template path based on database server version. + """ + + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold the connection object + conn_obj = args[0] + if 'template_path' not in kwargs or kwargs['template_path'] is None: + kwargs['template_path'] = 'row_security_policies/sql/#{0}#'.format( + conn_obj.manager.version) + + return f(*args, **kwargs) + + return wrap + + +@get_template_path +def get_parent(conn, tid, template_path=None): + """ + This function will return the parent of the given table. + :param conn: Connection Object + :param tid: Table oid + :param template_path: Optional template path + :return: + """ + + SQL = render_template("/".join([template_path, + 'get_parent.sql']), tid=tid) + status, rset = conn.execute_2darray(SQL) + if not status: + raise Exception(rset) + + schema = '' + table = '' + if 'rows' in rset and len(rset['rows']) > 0: + schema = rset['rows'][0]['schema'] + table = rset['rows'][0]['table'] + + return schema, table + + +@get_template_path +def get_sql(conn, data, did, tid, plid, datlastsysoid, schema, table, + mode=None, template_path=None): + """ + This function will generate sql from model data + """ + + if plid is not None: + sql = render_template("/".join( + [template_path, 'properties.sql']), plid=plid) + status, res = conn.execute_dict(sql) + if not status: + return internal_server_error(errormsg=res) + if len(res['rows']) == 0: + raise ObjectGone(_('Could not find the index in the table.')) + res_data = dict(res['rows'][0]) + + res = res_data + + old_data = res + old_data['schema'] = schema + old_data['table'] = table + sql = render_template( + "/".join([template_path, 'update.sql']), + data=data, o_data=old_data + ) + else: + data['schema'] = schema + data['table'] = table + sql = render_template("/".join( + [template_path, 'create.sql']), data=data) + return sql, data['name'] if 'name' in data else old_data['name'] + + +@get_template_path +def get_reverse_engineered_sql(conn, schema, table, did, tid, plid, + datlastsysoid, + template_path=None, with_header=True): + """ + This function will return reverse engineered sql for specified trigger. + + :param conn: Connection Object + :param schema: Schema + :param table: Table + :param did: DB ID + :param tid: Table ID + :param plid: Policy ID + :param datlastsysoid: + :param template_path: Optional template path + :param with_header: Optional parameter to decide whether the SQL will be + returned with header or not + :return: + """ + SQL = render_template("/".join( + [template_path, 'properties.sql']), plid=plid) + + status, res = conn.execute_dict(SQL) + if not status: + raise Exception(res) + + if len(res['rows']) == 0: + raise ObjectGone(_('Could not find the index in the table.')) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = schema + data['table'] = table + + SQL, name = get_sql(conn, data, did, tid, None, datlastsysoid, schema, + table) + + if with_header: + sql_header = u"-- POLICY: {0}\n\n-- ".format(data['name']) + + sql_header += render_template("/".join([template_path, + 'delete.sql']), + policy_name=data['name'], + result=data + ) + + SQL = sql_header + '\n\n' + SQL + + return SQL diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js index 0e73de872..4d08e27c7 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js @@ -468,7 +468,52 @@ define('pgadmin.node.table', [ return tbl_oid; }, }), - }, { + }, + { + id: 'rlspolicy', label: gettext('RLS Policy?'), cell: 'switch', + type: 'switch', mode: ['properties','edit', 'create'], + group: gettext('advanced'), + visible: function(m) { + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version >= 90500) + return true; + + return false; + }, + disabled: function(m) { + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version < 90500) + return true; + + return m.inSchema(); + }, + }, + { + id: 'forcerlspolicy', label: gettext('Force RLS Policy?'), cell: 'switch', + type: 'switch', mode: ['properties','edit', 'create'], deps: ['rlspolicy'], + group: gettext('advanced'), + visible: function(m) { + if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server) + && !_.isUndefined(m.node_info.server.version) && + m.node_info.server.version >= 90500) + return true; + + return false; + }, + disabled: function(m) { + if (m.get('rlspolicy')){ + return false; + } + setTimeout(function() { + m.set('forcerlspolicy', false); + }, 10); + + return true; + }, + }, + { id: 'advanced', label: gettext('Advanced'), type: 'group', visible: ShowAdvancedTab.show_advanced_tab, }, { @@ -1202,6 +1247,20 @@ define('pgadmin.node.table', [ this.errorModel.set('partition_keys', msg); return msg; } + if (this.get('rlspolicy') && this.isNew()){ + Alertify.confirm( + gettext('Check Policy?'), + gettext('Check if any policy exist. If no policy exists for the table, a default-deny policy is used, meaning that no rows are visible or can be modified'), + function() { + self.close(); + return true; + }, + function() { + // Do nothing. + return true; + } + ); + } this.errorModel.unset('partition_keys'); return null; }, diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/create.sql new file mode 100644 index 000000000..42b893968 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/create.sql @@ -0,0 +1,22 @@ +{# CREATE POLICY Statement #} + +-- POLICY: {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + +-- DROP POLICY {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }}; + +CREATE POLICY {{ data.name }} + ON {{conn|qtIdent(data.schema, data.table)}} +{% if data.event %} + FOR {{ data.event|upper }} +{% endif %} +{% if data.policyowner %} + TO {{ conn|qtTypeIdent(data.policyowner) }} +{% else %} + TO public +{% endif %} +{% if data.using %} + USING ({{ data.using }}) +{% endif %} +{% if data.withcheck %} + WITH CHECK ({{ data.withcheck }}) +{% endif %}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/delete.sql new file mode 100644 index 000000000..586e114b1 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/delete.sql @@ -0,0 +1 @@ +DROP POLICY {{ conn|qtIdent(policy_name) }} ON {{conn|qtIdent(result.schema, result.table)}}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_parent.sql new file mode 100644 index 000000000..aad3fa523 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::oid + WHERE rel.oid = {{tid}}::oid diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_policy_name.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_policy_name.sql new file mode 100644 index 000000000..82bcb51ec --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_policy_name.sql @@ -0,0 +1,9 @@ +{% if plid %} +SELECT + pl.oid AS oid, + pl.polname AS name +FROM + pg_policy pl +WHERE + pl.oid = {{ plid }} +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_position.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_position.sql new file mode 100644 index 000000000..0d67b1107 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/get_position.sql @@ -0,0 +1,2 @@ +SELECT pl.oid FROM pg_policy pl +WHERE pl.polrelid = {{tid}}::oid AND pl.polname = {{data.name|qtLiteral}}; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/nodes.sql new file mode 100644 index 000000000..083c01605 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/nodes.sql @@ -0,0 +1,13 @@ +SELECT + pl.oid AS oid, + pl.polname AS name +FROM + pg_policy pl +WHERE +{% if tid %} + pl.polrelid = {{ tid }} +{% elif plid %} + pl.oid = {{ plid }} +{% endif %} +ORDER BY + pl.polname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/properties.sql new file mode 100644 index 000000000..a83ca056c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/properties.sql @@ -0,0 +1,19 @@ +SELECT + pl.oid AS oid, + pl.polname AS name, + rw.cmd AS event, + rw.qual AS using, + rw.with_check AS withcheck, + array_to_string(rw.roles::name[], ', ') AS policyowner +FROM + pg_policy pl +JOIN pg_policies rw ON pl.polname=rw.policyname +WHERE +{% if plid %} + pl.oid = {{ plid }} +{% endif %} +{% if tid %} + pl.polrelid = {{ tid }} +{% endif %}; + + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/update.sql new file mode 100644 index 000000000..7a13874fd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/row_security_policies/sql/9.5_plus/update.sql @@ -0,0 +1,33 @@ +{#####################################################} +{## Change policy owner ##} +{#####################################################} +{% if data.policyowner and o_data.policyowner != data.policyowner %} +ALTER POLICY {{ o_data.name }} ON {{conn|qtIdent(o_data.schema, o_data.table)}} + TO {{ conn|qtTypeIdent(data.policyowner) }}; +{% endif %} + +{#####################################################} +{## Change policy using condition ##} +{#####################################################} +{% if data.using and o_data.withcheck != data.using %} +ALTER POLICY {{ o_data.name }} ON {{conn|qtIdent(o_data.schema, o_data.table)}} + USING ({{ data.using }}); +{% endif %} + +{#####################################################} +{## Change policy with check condition ##} +{#####################################################} +{% if data.withcheck and o_data.withcheck != data.withcheck %} +ALTER POLICY {{ o_data.name }} ON {{conn|qtIdent(o_data.schema, o_data.table)}} + WITH CHECK ({{ data.withcheck }}); +{% endif %} + +{#####################################################} +{## Change policy name ##} +{#####################################################} +{% if data.name and o_data.name != data.name %} +ALTER POLICY {{ o_data.name }} ON {{conn|qtIdent(o_data.schema, o_data.table)}} + RENAME TO {{ conn|qtIdent(data.name) }}; +{% endif %} + + diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql index 0e0fdd263..925f313d1 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/create.sql @@ -104,6 +104,23 @@ TABLESPACE {{ conn|qtIdent(data.spcname) }}; ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} OWNER to {{conn|qtIdent(data.relowner)}}; {% endif %} + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% endif %} + {### Security Labels on Table ###} {% if data.seclabels and data.seclabels|length > 0 %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql index 7c3a67c9a..d52ec2a63 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql @@ -50,7 +50,7 @@ SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS r substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, CASE WHEN typ.typname IS NOT NULL THEN (select quote_ident(nspname) FROM pg_namespace WHERE oid = {{scid}}::oid )||'.'||quote_ident(typ.typname) ELSE typ.typname END AS typname, - typ.typrelid AS typoid, + typ.typrelid AS typoid, rel.relrowsecurity as rlspolicy, rel.relforcerowsecurity as forcerlspolicy, (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels, (CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/create.sql index 73717cd2d..f7cef12cd 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/create.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/create.sql @@ -105,6 +105,23 @@ TABLESPACE {{ conn|qtIdent(data.spcname) }}; ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} OWNER to {{conn|qtIdent(data.relowner)}}; {% endif %} + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% endif %} + {### Security Labels on Table ###} {% if data.seclabels and data.seclabels|length > 0 %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql index f874f52ac..82f9a4358 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql @@ -51,7 +51,7 @@ SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS r substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, CASE WHEN typ.typname IS NOT NULL THEN (select quote_ident(nspname) FROM pg_namespace WHERE oid = {{scid}}::oid )||'.'||quote_ident(typ.typname) ELSE typ.typname END AS typname, - typ.typrelid AS typoid, + typ.typrelid AS typoid, rel.relrowsecurity as rlspolicy, rel.relforcerowsecurity as forcerlspolicy, (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels, (CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql index d5b9d367c..48ec03494 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql @@ -212,6 +212,30 @@ COMMENT ON TABLE {{conn|qtIdent(data.schema, data.name)}} IS {{data.description|qtLiteral}}; {% endif %} + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% elif data.rlspolicy is defined and data.rlspolicy != o_data.rlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + DISABLE ROW LEVEL SECURITY; + +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% elif data.forcerlspolicy is defined and data.forcerlspolicy != o_data.forcerlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + NO FORCE ROW LEVEL SECURITY; +{% endif %} + {#####################################################} {## Update table Privileges ##} {#####################################################} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql index 79d5d74b6..383f96e4e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql @@ -123,6 +123,23 @@ TABLESPACE {{ conn|qtIdent(data.spcname) }}; ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} OWNER to {{conn|qtIdent(data.relowner)}}; {% endif %} + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% endif %} + {### Security Labels on Table ###} {% if data.seclabels and data.seclabels|length > 0 %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql index 1a7aec47e..5a2dbb4a8 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql @@ -51,7 +51,7 @@ SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS r substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, CASE WHEN typ.typname IS NOT NULL THEN (select quote_ident(nspname) FROM pg_namespace WHERE oid = {{scid}}::oid )||'.'||quote_ident(typ.typname) ELSE typ.typname END AS typname, - typ.typrelid AS typoid, + typ.typrelid AS typoid, rel.relrowsecurity as rlspolicy, rel.relforcerowsecurity as forcerlspolicy, (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels, (CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql index b8b4da665..25e1b6295 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql @@ -50,6 +50,31 @@ ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} SET TABLESPACE {{conn|qtIdent(data.spcname)}}; {% endif %} + + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% elif data.rlspolicy is defined and data.rlspolicy != o_data.rlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + DISABLE ROW LEVEL SECURITY; + +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% elif data.forcerlspolicy is defined and data.forcerlspolicy != o_data.forcerlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + NO FORCE ROW LEVEL SECURITY; +{% endif %} + {#####################################################} {## change fillfactor settings ##} {#####################################################} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/create.sql index 9f32fb855..97c0b735b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/create.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/create.sql @@ -92,6 +92,24 @@ TABLESPACE {{ conn|qtIdent(data.spcname) }}; ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} OWNER to {{conn|qtIdent(data.relowner)}}; {% endif %} + + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% endif %} + {### Security Labels on Table ###} {% if data.seclabels and data.seclabels|length > 0 %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/properties.sql index 444fe27c0..e4ac4544c 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/properties.sql @@ -49,7 +49,7 @@ SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS r substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, rel.reloftype, CASE WHEN typ.typname IS NOT NULL THEN (select quote_ident(nspname) FROM pg_namespace WHERE oid = {{scid}}::oid )||'.'||quote_ident(typ.typname) ELSE typ.typname END AS typname, - typ.typrelid AS typoid, + typ.typrelid AS typoid,rel.relrowsecurity as rlspolicy, rel.relforcerowsecurity as forcerlspolicy, (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, (SELECT array_agg(provider || '=' || label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS seclabels, (CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/update.sql index fc44fe8bc..28f25e925 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/update.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/9.6_plus/update.sql @@ -42,6 +42,31 @@ ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} {% endfor %} {% endif %} + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% elif data.rlspolicy is defined and data.rlspolicy != o_data.rlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + DISABLE ROW LEVEL SECURITY; + +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% elif data.forcerlspolicy is defined and data.forcerlspolicy != o_data.forcerlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + NO FORCE ROW LEVEL SECURITY; + +{% endif %} + {#####################################################} {## Change hasOID attribute of table ##} {#####################################################} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql index 9553d99bb..ffce63e55 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/create.sql @@ -171,3 +171,18 @@ EXTERNAL{% elif c.attstorage == 'x'%}EXTENDED{% endif %}; {{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.foreign_key)}} {{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.check_constraint)}} {{CONSTRAINTS.CONSTRAINT_COMMENTS(conn, data.schema, data.name, data.exclude_constraint)}} +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/properties.sql index b5c3c2527..f7f35870f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/properties.sql @@ -50,7 +50,7 @@ FROM ( substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age, substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age, rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions, NULL AS reloftype, NULL AS typname, - typ.typrelid AS typoid, + typ.typrelid AS typoid, rel.relrowsecurity as rlspolicy, rel.relforcerowsecurity as forcerlspolicy, (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable, ARRAY[]::varchar[] AS seclabels, (CASE WHEN rel.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_table diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql index 09817343f..4d84babce 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql @@ -125,6 +125,30 @@ ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} RESET ( ); {% endif %} {% endif %} + +{#####################################################} +{## Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.rlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + ENABLE ROW LEVEL SECURITY; +{% elif data.rlspolicy is defined and data.rlspolicy != o_data.rlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + DISABLE ROW LEVEL SECURITY; + +{% endif %} + +{#####################################################} +{## Force Enable Row Level Security Policy on table ##} +{#####################################################} +{% if data.forcerlspolicy %} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + FORCE ROW LEVEL SECURITY; +{% elif data.forcerlspolicy is defined and data.forcerlspolicy != o_data.forcerlspolicy%} +ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} + NO FORCE ROW LEVEL SECURITY; +{% endif %} + {#####################################} {## Toast table AutoVacuum settings ##} {#####################################} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py index b22dfe12b..4cb1e5bcd 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py @@ -42,6 +42,9 @@ from pgadmin.browser.server_groups.servers.databases.schemas.tables.\ triggers import utils as trigger_utils from pgadmin.browser.server_groups.servers.databases.schemas.tables.\ compound_triggers import utils as compound_trigger_utils +from pgadmin.browser.server_groups.servers.databases.schemas. \ + tables.row_security_policies import \ + utils as row_security_policies_utils class BaseTableView(PGChildNodeView, BasePartitionTable): @@ -121,6 +124,10 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): self.index_template_path = compile_template_path( 'indexes/sql', server_type, ver) + # Template for index node + self.row_security_policies_template_path = \ + 'row_security_policies/sql/#{0}#'.format(ver) + # Template for trigger node self.trigger_template_path = \ 'triggers/sql/{0}/#{1}#'.format(server_type, ver) @@ -511,6 +518,33 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): main_sql.append(index_sql.strip('\n')) + """ + ######################################################## + # 2) Reverse engineered sql for ROW SECURITY POLICY + ######################################################## + """ + if self.manager.version >= 90500: + SQL = \ + render_template( + "/".join([self.row_security_policies_template_path, + 'nodes.sql']), tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + policy_sql = row_security_policies_utils. \ + get_reverse_engineered_sql( + self.conn, schema, table, did, tid, row['oid'], + self.datlastsysoid, + template_path=None, with_header=json_resp) + policy_sql = u"\n" + policy_sql + + # Add into main sql + policy_sql = re.sub('\n{2,}', '\n\n', policy_sql) + + main_sql.append(policy_sql.strip('\n')) + """ ######################################## # 3) Reverse engineered sql for TRIGGERS diff --git a/web/webpack.config.js b/web/webpack.config.js index c87135cd1..3237e5e79 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -460,6 +460,7 @@ module.exports = [{ ',pgadmin.node.type' + ',pgadmin.node.rule' + ',pgadmin.node.index' + + ',pgadmin.node.row_security_policy' + ',pgadmin.node.trigger' + ',pgadmin.node.catalog_object_column' + ',pgadmin.node.view' + diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 2efcfb451..9854f0deb 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -261,6 +261,7 @@ var webpackShimConfig = { 'pgadmin.node.unique_constraint': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint'), 'pgadmin.node.user_mapping': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mappings/static/js/user_mapping'), 'pgadmin.node.view': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view'), + 'pgadmin.node.row_security_policy': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/js/row_security_policy'), 'pgadmin.preferences': path.join(__dirname, './pgadmin/preferences/static/js/preferences'), 'pgadmin.settings': path.join(__dirname, './pgadmin/settings/static/js/settings'), 'pgadmin.server.supported_servers': '/browser/server/supported_servers',