Permalink
Browse files

Add support for changing column-level privileges.

 * docs/column.rst: Add automethod for new methods.
 * pyrseas/dbobject/column.py (Column.add_privs): New method to add
   GRANTs for columns.  (Column.diff_privileges): New method to
   generate GRANT/REVOKE statements at the column level.
   (ColumnDict.from_map): Map column-level privileges.
 * pyrseas/dbobject/privileges.py ({_expand_priv_lists, add_grant,
   add_revoke, diff_privs}): Add sub-object argument to handle column
   names.
 * pyrseas/dbobject/table.py (Table.create): Remove grantable
   decorator; add to handle both table and column-level privileges.
   (Table.diff_map): Process column-level privileges.
   (TableDict.from_map): Map owner information before handling
   columns.
 * tests/dbobject/test_privs.py: Add tests for verifying new support.
  • Loading branch information...
1 parent a371841 commit b055aa01d203cb2469fd87dd581030646e698bdd @jmafc jmafc committed Aug 22, 2012
Showing with 148 additions and 39 deletions.
  1. +4 −0 docs/column.rst
  2. +43 −8 pyrseas/dbobject/column.py
  3. +24 −14 pyrseas/dbobject/privileges.py
  4. +17 −6 pyrseas/dbobject/table.py
  5. +60 −11 tests/dbobject/test_privs.py
View
@@ -25,6 +25,10 @@ attribute is also present but is not made visible externally.
.. automethod:: Column.add
+.. automethod:: Column.add_privs
+
+.. automethod:: Column.diff_privileges
+
.. automethod:: Column.comment
.. automethod:: Column.drop
@@ -6,7 +6,10 @@
This module defines two classes: Column derived from
DbSchemaObject and ColumnDict derived from DbObjectDict.
"""
-from pyrseas.dbobject import DbObjectDict, DbSchemaObject, quote_id
+from pyrseas.dbobject import DbObjectDict, DbSchemaObject
+from pyrseas.dbobject import quote_id, grantable
+from pyrseas.dbobject.privileges import privileges_from_map, add_grant
+from pyrseas.dbobject.privileges import diff_privs
class Column(DbSchemaObject):
@@ -49,6 +52,30 @@ def add(self):
return (stmt, '' if not hasattr(self, 'description')
else self.comment())
+ def add_privs(self):
+ """Generate SQL statements to grant privileges on new column
+
+ :return: list of SQL statements
+ """
+ stmts = []
+ if hasattr(self, 'privileges'):
+ for priv in self.privileges:
+ stmts.append(add_grant(self._table, priv, self.name))
+ return stmts
+
+ def diff_privileges(self, incol):
+ """Generate SQL statements to grant or revoke privileges
+
+ :param incol: a YAML map defining the input column
+ :return: list of SQL statements
+ """
+ stmts = []
+ currprivs = self.privileges if hasattr(self, 'privileges') else {}
+ newprivs = incol.privileges if hasattr(incol, 'privileges') else {}
+ stmts.append(diff_privs(self._table, currprivs, incol._table, newprivs,
+ self.name))
+ return stmts
+
def comment(self):
"""Return a SQL COMMENT statement for the column
@@ -199,14 +226,22 @@ def from_map(self, table, incols):
raise ValueError("Table '%s' has no columns" % table.name)
cols = self[(table.schema, table.name)] = []
- for col in incols:
- for key in list(col.keys()):
- if isinstance(col[key], dict):
- arg = col[key]
+ for incol in incols:
+ for key in list(incol.keys()):
+ if isinstance(incol[key], dict):
+ arg = incol[key]
else:
- arg = {'type': col[key]}
- cols.append(Column(schema=table.schema, table=table.name,
- name=key, **arg))
+ arg = {'type': incol[key]}
+ col = Column(schema=table.schema, table=table.name, name=key,
+ **arg)
+ if hasattr(col, 'privileges'):
+ if not hasattr(table, 'owner'):
+ raise ValueError("Column '%s.%s' has privileges but "
+ "no owner information" % (
+ table.name, key))
+ col.privileges = privileges_from_map(
+ col.privileges, col.allprivs, table.owner)
+ cols.append(col)
def diff_map(self, incols):
"""Generate SQL to transform existing columns
@@ -25,25 +25,28 @@ def _split_privs(privspec):
return (usr, privcodes, grantor)
-def _expand_priv_lists(obj, privcodes):
+def _expand_priv_lists(obj, privcodes, subobj):
"""Convert privilege code strings to expanded lists
:param obj: the object on which the privilege is granted
- :privcodes: string of privilege codes
+ :param privcodes: string of privilege codes
+ :param subobj: sub-object name (e.g., column name)
:return: tuple of lists with decoded privileges
"""
privs = []
wgo = []
if privcodes == obj.allprivs and len(obj.allprivs) > 1:
privs = ['ALL']
else:
+ if subobj:
+ subobj = ' (%s)' % subobj
for code in sorted(PRIVCODES.keys()):
if code in privcodes:
- priv = PRIVCODES[code]
+ priv = PRIVCODES[code].upper() + subobj
if code + '*' in privcodes:
- wgo.append(priv.upper())
+ wgo.append(priv)
else:
- privs.append(priv.upper())
+ privs.append(priv)
return (privs, wgo)
@@ -112,15 +115,16 @@ def privileges_from_map(privlist, allprivs, owner):
return retlist
-def add_grant(obj, privspec):
+def add_grant(obj, privspec, subobj=''):
"""Return GRANT statements on the object based on the privilege spec
:param obj: the object on which the privilege is granted
:param privspec: the privilege specification (aclitem)
+ :param subobj: sub-object name (e.g., column name)
:return: list of GRANT statements
"""
(usr, privcodes, grantor) = _split_privs(privspec)
- (privs, wgo) = _expand_priv_lists(obj, privcodes)
+ (privs, wgo) = _expand_priv_lists(obj, privcodes, subobj)
objtype = obj.objtype
if hasattr(obj, 'privobjtype'):
objtype = obj.privobjtype
@@ -134,15 +138,16 @@ def add_grant(obj, privspec):
return stmts
-def add_revoke(obj, privspec):
+def add_revoke(obj, privspec, subobj=''):
"""Return REVOKE statements on the object based on the privilege spec
:param obj: the object on which the privilege is to be revoked
:param privspec: the privilege specification (aclitem)
+ :param subobj: sub-object name (e.g., column name)
:return: list of REVOKE statements
"""
(usr, privcodes, grantor) = _split_privs(privspec)
- (privs, wgo) = _expand_priv_lists(obj, privcodes)
+ (privs, wgo) = _expand_priv_lists(obj, privcodes, subobj)
objtype = obj.objtype
if hasattr(obj, 'privobjtype'):
objtype = obj.privobjtype
@@ -156,13 +161,14 @@ def add_revoke(obj, privspec):
return stmts
-def diff_privs(currobj, currlist, newobj, newlist):
+def diff_privs(currobj, currlist, newobj, newlist, subobj=''):
"""Return GRANT or REVOKE statements to adjust object privileges
:param currobj: current object
:param currlist: list of current privileges
:param newobj: new object
:param newlist: list of new privileges
+ :param subobj: sub-object (e.g., column name)
:return: list of GRANT and REVOKE statements
"""
def rejoin(privdict, usr, grantor):
@@ -180,12 +186,16 @@ def rejoin(privdict, usr, grantor):
newprivs[(usr, grantor)] = privcodes
for (usr, gtor) in list(currprivs.keys()):
if (usr, gtor) not in newprivs:
- stmts.append(add_revoke(currobj, rejoin(currprivs, usr, gtor)))
+ stmts.append(add_revoke(currobj, rejoin(currprivs, usr, gtor),
+ subobj))
for (usr, gtor) in list(newprivs.keys()):
if (usr, gtor) not in currprivs:
- stmts.append(add_grant(newobj, rejoin(newprivs, usr, gtor)))
+ stmts.append(add_grant(newobj, rejoin(newprivs, usr, gtor),
+ subobj))
else:
if currprivs[(usr, gtor)] != newprivs[(usr, gtor)]:
- stmts.append(add_revoke(currobj, rejoin(currprivs, usr, gtor)))
- stmts.append(add_grant(newobj, rejoin(newprivs, usr, gtor)))
+ stmts.append(add_revoke(currobj, rejoin(currprivs, usr, gtor),
+ subobj))
+ stmts.append(add_grant(newobj, rejoin(newprivs, usr, gtor),
+ subobj))
return stmts
@@ -14,7 +14,7 @@
from pyrseas.dbobject import commentable, ownable, grantable
from pyrseas.dbobject.constraint import CheckConstraint, PrimaryKey
from pyrseas.dbobject.constraint import ForeignKey, UniqueConstraint
-from pyrseas.dbobject.privileges import privileges_from_map
+from pyrseas.dbobject.privileges import privileges_from_map, add_grant
MAX_BIGINT = 9223372036854775807
@@ -269,7 +269,6 @@ def to_map(self, dbschemas, no_owner, no_privs):
return {self.extern_key(): tbl}
- @grantable
def create(self):
"""Return SQL statements to CREATE the table
@@ -279,9 +278,11 @@ def create(self):
if hasattr(self, 'created'):
return stmts
cols = []
+ colprivs = []
for col in self.columns:
if not (hasattr(col, 'inherited') and col.inherited):
cols.append(" " + col.add()[0])
+ colprivs.append(col.add_privs())
inhclause = ''
if hasattr(self, 'inherits'):
inhclause = " INHERITS (%s)" % ", ".join(t for t in self.inherits)
@@ -292,6 +293,11 @@ def create(self):
self.qualname(), ",\n".join(cols), inhclause, tblspc))
if hasattr(self, 'owner'):
stmts.append(self.alter_owner())
+ if hasattr(self, 'privileges'):
+ for priv in self.privileges:
+ stmts.append(add_grant(self, priv))
+ if colprivs:
+ stmts.append(colprivs)
if hasattr(self, 'description'):
stmts.append(self.comment())
for col in self.columns:
@@ -331,6 +337,7 @@ def diff_map(self, intable):
if not hasattr(col, 'dropped')]
dbcols = len(colnames)
+ colprivs = []
base = "ALTER %s %s\n " % (self.objtype, self.qualname())
# check input columns
for (num, incol) in enumerate(intable.columns):
@@ -342,20 +349,24 @@ def diff_map(self, intable):
(stmt, descr) = self.columns[num].diff_map(incol)
if stmt:
stmts.append(base + stmt)
+ colprivs.append(self.columns[num].diff_privileges(incol))
if descr:
stmts.append(descr)
# add new columns
elif incol.name not in colnames and \
not hasattr(incol, 'inherited'):
(stmt, descr) = incol.add()
stmts.append(base + "ADD COLUMN %s" % stmt)
+ colprivs.append(incol.add_privs())
if descr:
stmts.append(descr)
if hasattr(intable, 'owner'):
if intable.owner != self.owner:
stmts.append(self.alter_owner(intable.owner))
stmts.append(self.diff_privileges(intable))
+ if colprivs:
+ stmts.append(colprivs)
if hasattr(intable, 'tablespace'):
if not hasattr(self, 'tablespace') \
or self.tablespace != intable.tablespace:
@@ -486,15 +497,15 @@ def from_map(self, schema, inobjs, newdb):
intable = inobj
if not intable:
raise ValueError("Table '%s' has no specification" % k)
+ for attr in ['inherits', 'owner', 'tablespace', 'oldname',
+ 'description']:
+ if attr in intable:
+ setattr(table, attr, intable[attr])
try:
newdb.columns.from_map(table, intable['columns'])
except KeyError as exc:
exc.args = ("Table '%s' has no columns" % key, )
raise
- for attr in ['inherits', 'owner', 'tablespace', 'oldname',
- 'description']:
- if attr in intable:
- setattr(table, attr, intable[attr])
newdb.constraints.from_map(table, intable)
if 'indexes' in intable:
newdb.indexes.from_map(table, intable['indexes'])
Oops, something went wrong. Retry.

0 comments on commit b055aa0

Please sign in to comment.