Added support of Row Security Policies. Fixes #5516

This commit is contained in:
Pradip Parkale 2020-06-15 15:44:59 +05:30 committed by Akshay Joshi
parent bfa0b87791
commit 18277543b6
47 changed files with 2678 additions and 6 deletions

View File

@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #5516 <https://redmine.postgresql.org/issues/5516>`_ - Added support of Row Security Policies.
| `Issue #5576 <https://redmine.postgresql.org/issues/5576>`_ - Improve error messaging if the storage and log directories cannot be created.
Housekeeping

View File

@ -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:

View File

@ -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)

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:#DDF99D;stroke:#1A6016;stroke-width:1.131;stroke-miterlimit:10;}
.st1{fill:#1A6016;}
</style>
<g>
<g>
<path class="st0" d="M6.3,9V7.2c0-2.5,1.8-4.6,4.1-4.6l0,0c2.3,0,4.1,2,4.1,4.8v1.8"/>
<g>
<g>
<path class="st0" d="M16.1,20.4H4.8c-0.9,0-1.6-0.7-1.6-1.6v-8.1c0-0.9,0.7-1.6,1.6-1.6h11.3c0.9,0,1.6,0.7,1.6,1.6V19
C17.8,19.9,17,20.4,16.1,20.4z"/>
</g>
<g>
<path class="st1" d="M10.4,16.9L10.4,16.9c-1.2,0-2.1-0.9-2.1-2.1l0,0c0-1.2,0.9-2.1,2.1-2.1l0,0c1.2,0,2.1,0.9,2.1,2.1l0,0
C12.6,16,11.6,16.9,10.4,16.9z"/>
</g>
</g>
</g>
<g>
<path class="st0" d="M9.4,10V8.2c0-2.5,1.8-4.6,4.1-4.6l0,0c2.3,0,4.1,2,4.1,4.8v1.8"/>
<g>
<g>
<path class="st0" d="M19.1,21.4H7.8c-0.9,0-1.6-0.7-1.6-1.6v-8.1c0-0.9,0.7-1.6,1.6-1.6h11.3c0.9,0,1.6,0.7,1.6,1.6V20
C20.8,20.9,20,21.4,19.1,21.4z"/>
</g>
<g>
<path class="st1" d="M13.4,17.9L13.4,17.9c-1.2,0-2.1-0.9-2.1-2.1l0,0c0-1.2,0.9-2.1,2.1-2.1l0,0c1.2,0,2.1,0.9,2.1,2.1l0,0
C15.6,17,14.6,17.9,13.4,17.9z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#DDF99D;stroke:#1A6016;stroke-width:0.6165;stroke-miterlimit:10;}
.st1{fill:#1A6016;}
</style>
<g>
<path class="st0" d="M5.2,6.4V5.2C5.2,3.5,6.4,2,8,2l0,0c1.6,0,2.8,1.4,2.8,3.2v1.2"/>
<g>
<g>
<path class="st0" d="M11.9,14H4.1C3.4,14,3,13.5,3,13V7.4c0-0.6,0.5-1,1.1-1h7.9c0.6,0,1.1,0.5,1.1,1v5.5
C13.1,13.5,12.5,14,11.9,14z"/>
</g>
<g>
<path class="st1" d="M8,11.4L8,11.4c-0.7,0-1.2-0.5-1.2-1.2l0,0C6.8,9.5,7.3,9,8,9l0,0c0.7,0,1.2,0.5,1.2,1.2l0,0
C9.2,10.9,8.7,11.4,8,11.4z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 904 B

View File

@ -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'];
});

View File

@ -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 []

View File

@ -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
;

View File

@ -0,0 +1,2 @@
ALTER POLICY test ON public.test_emp_rule
RENAME TO policy_1;

View File

@ -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
;

View File

@ -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
;

View File

@ -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
;

View File

@ -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_$%{}[]()&*^!@\"'`\\/#"
}
}
]
}

View File

@ -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
}
}
]
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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;
},

View File

@ -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 %};

View File

@ -0,0 +1 @@
DROP POLICY {{ conn|qtIdent(policy_name) }} ON {{conn|qtIdent(result.schema, result.table)}};

View File

@ -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

View File

@ -0,0 +1,9 @@
{% if plid %}
SELECT
pl.oid AS oid,
pl.polname AS name
FROM
pg_policy pl
WHERE
pl.oid = {{ plid }}
{% endif %}

View File

@ -0,0 +1,2 @@
SELECT pl.oid FROM pg_policy pl
WHERE pl.polrelid = {{tid}}::oid AND pl.polname = {{data.name|qtLiteral}};

View File

@ -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;

View File

@ -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 %};

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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

View File

@ -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 %}

View File

@ -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

View File

@ -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 ##}
{#####################################################}

View File

@ -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 %}

View File

@ -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

View File

@ -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 ##}
{#####################################################}

View File

@ -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 %}

View File

@ -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

View File

@ -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 ##}
{#####################################################}

View File

@ -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 %}

View File

@ -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

View File

@ -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 ##}
{#####################################}

View File

@ -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

View File

@ -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' +

View File

@ -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',