mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support of Row Security Policies. Fixes #5516
This commit is contained in:
parent
bfa0b87791
commit
18277543b6
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
@ -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 |
@ -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 |
@ -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'];
|
||||
});
|
@ -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 []
|
@ -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
|
||||
;
|
||||
|
@ -0,0 +1,2 @@
|
||||
ALTER POLICY test ON public.test_emp_rule
|
||||
RENAME TO policy_1;
|
@ -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
|
||||
;
|
||||
|
@ -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
|
||||
;
|
||||
|
@ -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
|
||||
;
|
||||
|
@ -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_$%{}[]()&*^!@\"'`\\/#"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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
|
@ -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;
|
||||
},
|
||||
|
@ -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 %};
|
@ -0,0 +1 @@
|
||||
DROP POLICY {{ conn|qtIdent(policy_name) }} ON {{conn|qtIdent(result.schema, result.table)}};
|
@ -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
|
@ -0,0 +1,9 @@
|
||||
{% if plid %}
|
||||
SELECT
|
||||
pl.oid AS oid,
|
||||
pl.polname AS name
|
||||
FROM
|
||||
pg_policy pl
|
||||
WHERE
|
||||
pl.oid = {{ plid }}
|
||||
{% endif %}
|
@ -0,0 +1,2 @@
|
||||
SELECT pl.oid FROM pg_policy pl
|
||||
WHERE pl.polrelid = {{tid}}::oid AND pl.polname = {{data.name|qtLiteral}};
|
@ -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;
|
@ -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 %};
|
||||
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ##}
|
||||
{#####################################################}
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ##}
|
||||
{#####################################################}
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ##}
|
||||
{#####################################################}
|
||||
|
@ -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 %}
|
||||
|
@ -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
|
||||
|
@ -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 ##}
|
||||
{#####################################}
|
||||
|
@ -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
|
||||
|
@ -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' +
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user