Apply new permissions model to SQLAlchemy backend.
authorAntony Chazapis <chazapis@gmail.com>
Fri, 10 Feb 2012 18:27:07 +0000 (20:27 +0200)
committerAntony Chazapis <chazapis@gmail.com>
Fri, 10 Feb 2012 18:27:07 +0000 (20:27 +0200)
Refs #1984

pithos/backends/lib/sqlalchemy/__init__.py
pithos/backends/lib/sqlalchemy/node.py
pithos/backends/lib/sqlalchemy/permissions.py
pithos/backends/lib/sqlalchemy/xfeatures.py
pithos/backends/lib/sqlite/permissions.py
pithos/backends/lib/sqlite/xfeatures.py

index 49ada08..c19bf96 100644 (file)
 # or implied, of GRNET S.A.
 
 from dbwrapper import DBWrapper
-from node import Node, ROOTNODE, SERIAL, HASH, SIZE, MTIME, MUSER, UUID, CLUSTER
+from node import Node, ROOTNODE, SERIAL, HASH, SIZE, TYPE, MTIME, MUSER, UUID, CLUSTER, MATCH_PREFIX, MATCH_EXACT
 from permissions import Permissions, READ, WRITE
 
 __all__ = ["DBWrapper",
-           "Node", "ROOTNODE", "SERIAL", "HASH", "SIZE", "MTIME", "MUSER", "UUID", "CLUSTER",
+           "Node", "ROOTNODE", "SERIAL", "HASH", "SIZE", "TYPE", "MTIME", "MUSER", "UUID", "CLUSTER", "MATCH_PREFIX", "MATCH_EXACT",
            "Permissions", "READ", "WRITE"]
 
index e25a7ae..3a4ae4e 100644 (file)
@@ -46,7 +46,9 @@ from pithos.lib.filter import parse_filters
 
 ROOTNODE  = 0
 
-( SERIAL, NODE, HASH, SIZE, SOURCE, MTIME, MUSER, UUID, CLUSTER ) = range(9)
+( SERIAL, NODE, HASH, SIZE, TYPE, SOURCE, MTIME, MUSER, UUID, CLUSTER ) = range(10)
+
+( MATCH_PREFIX, MATCH_EXACT ) = range(2)
 
 inf = float('inf')
 
@@ -90,11 +92,12 @@ _propnames = {
     'node'      : 1,
     'hash'      : 2,
     'size'      : 3,
-    'source'    : 4,
-    'mtime'     : 5,
-    'muser'     : 6,
-    'uuid'      : 7,
-    'cluster'   : 8
+    'type'      : 4,
+    'source'    : 5,
+    'mtime'     : 6,
+    'muser'     : 7,
+    'uuid'      : 8,
+    'cluster'   : 9
 }
 
 
@@ -157,6 +160,7 @@ class Node(DBWorker):
                                          onupdate='CASCADE')))
         columns.append(Column('hash', String(255)))
         columns.append(Column('size', BigInteger, nullable=False, default=0))
+        columns.append(Column('type', String(255), nullable=False, default=''))
         columns.append(Column('source', Integer))
         columns.append(Column('mtime', DECIMAL(precision=16, scale=6)))
         columns.append(Column('muser', String(255), nullable=False, default=''))
@@ -229,13 +233,14 @@ class Node(DBWorker):
     def node_get_versions(self, node, keys=(), propnames=_propnames):
         """Return the properties of all versions at node.
            If keys is empty, return all properties in the order
-           (serial, node, hash, size, source, mtime, muser, uuid, cluster).
+           (serial, node, hash, size, type, source, mtime, muser, uuid, cluster).
         """
         
         s = select([self.versions.c.serial,
                     self.versions.c.node,
                     self.versions.c.hash,
                     self.versions.c.size,
+                    self.versions.c.type,
                     self.versions.c.source,
                     self.versions.c.mtime,
                     self.versions.c.muser,
@@ -488,6 +493,7 @@ class Node(DBWorker):
                     self.versions.c.node,
                     self.versions.c.hash,
                     self.versions.c.size,
+                    self.versions.c.type,
                     self.versions.c.source,
                     self.versions.c.mtime,
                     self.versions.c.muser,
@@ -550,13 +556,13 @@ class Node(DBWorker):
         mtime = max(mtime, r[2])
         return (count, size, mtime)
     
-    def version_create(self, node, hash, size, source, muser, uuid, cluster=0):
+    def version_create(self, node, hash, size, type, source, muser, uuid, cluster=0):
         """Create a new version from the given properties.
            Return the (serial, mtime) of the new version.
         """
         
         mtime = time()
-        s = self.versions.insert().values(node=node, hash=hash, size=size, source=source,
+        s = self.versions.insert().values(node=node, hash=hash, size=size, type=type, source=source,
                                           mtime=mtime, muser=muser, uuid=uuid, cluster=cluster)
         serial = self.conn.execute(s).inserted_primary_key[0]
         self.statistics_update_ancestors(node, 1, size, mtime, cluster)
@@ -565,14 +571,15 @@ class Node(DBWorker):
     def version_lookup(self, node, before=inf, cluster=0):
         """Lookup the current version of the given node.
            Return a list with its properties:
-           (serial, node, hash, size, source, mtime, muser, uuid, cluster)
+           (serial, node, hash, size, type, source, mtime, muser, uuid, cluster)
            or None if the current version is not found in the given cluster.
         """
         
         v = self.versions.alias('v')
         s = select([v.c.serial, v.c.node, v.c.hash,
-                    v.c.size, v.c.source, v.c.mtime,
-                    v.c.muser, v.c.uuid, v.c.cluster])
+                    v.c.size, v.c.type, v.c.source,
+                    v.c.mtime, v.c.muser, v.c.uuid,
+                    v.c.cluster])
         c = select([func.max(self.versions.c.serial)],
             self.versions.c.node == node)
         if before != inf:
@@ -590,13 +597,14 @@ class Node(DBWorker):
         """Return a sequence of values for the properties of
            the version specified by serial and the keys, in the order given.
            If keys is empty, return all properties in the order
-           (serial, node, hash, size, source, mtime, muser, uuid, cluster).
+           (serial, node, hash, size, type, source, mtime, muser, uuid, cluster).
         """
         
         v = self.versions.alias()
         s = select([v.c.serial, v.c.node, v.c.hash,
-                    v.c.size, v.c.source, v.c.mtime,
-                    v.c.muser, v.c.uuid, v.c.cluster], v.c.serial == serial)
+                    v.c.size, v.c.type, v.c.source,
+                    v.c.mtime, v.c.muser, v.c.uuid,
+                    v.c.cluster], v.c.serial == serial)
         rp = self.conn.execute(s)
         r = rp.fetchone()
         rp.close()
@@ -748,8 +756,11 @@ class Node(DBWorker):
         s = s.where(a.c.domain == domain)
         s = s.where(n.c.node == v.c.node)
         conj = []
-        for x in pathq:
-            conj.append(n.c.path.like(self.escape_like(x) + '%', escape='\\'))
+        for path, match in pathq:
+            if match == MATCH_PREFIX:
+                conj.append(n.c.path.like(self.escape_like(path) + '%', escape='\\'))
+            elif match == MATCH_EXACT:
+                conj.append(n.c.path == path)
         if conj:
             s = s.where(or_(*conj))
         rp = self.conn.execute(s)
@@ -826,8 +837,11 @@ class Node(DBWorker):
         s = s.where(n.c.node == v.c.node)
         s = s.where(and_(n.c.path > bindparam('start'), n.c.path < nextling))
         conj = []
-        for x in pathq:
-            conj.append(n.c.path.like(self.escape_like(x) + '%', escape='\\'))
+        for path, match in pathq:
+            if match == MATCH_PREFIX:
+                conj.append(n.c.path.like(self.escape_like(path) + '%', escape='\\'))
+            elif match == MATCH_EXACT:
+                conj.append(n.c.path == path)
         if conj:
             s = s.where(or_(*conj))
         
index 2f54aaa..f6c74e5 100644 (file)
@@ -58,17 +58,39 @@ class Permissions(XFeatures, Groups, Public):
         if not members:
             return
         feature = self.xfeature_create(path)
-        if feature is None:
-            return
         self.feature_setmany(feature, access, members)
     
     def access_set(self, path, permissions):
         """Set permissions for path. The permissions dict
            maps 'read', 'write' keys to member lists."""
         
-        self.xfeature_destroy(path)
-        self.access_grant(path, READ, permissions.get('read', []))
-        self.access_grant(path, WRITE, permissions.get('write', []))
+        r = permissions.get('read', [])
+        w = permissions.get('write', [])
+        if not r and not w:
+            self.xfeature_destroy(path)
+            return
+        feature = self.xfeature_create(path)
+        if r:
+            self.feature_clear(feature, READ)
+            self.feature_setmany(feature, READ, r)
+        if w:
+            self.feature_clear(feature, WRITE)
+            self.feature_setmany(feature, WRITE, w)
+    
+    def access_get(self, path):
+        """Get permissions for path."""
+        
+        feature = self.xfeature_get(path)
+        if not feature:
+            return {}
+        permissions = self.feature_dict(feature)
+        if READ in permissions:
+            permissions['read'] = permissions[READ]
+            del(permissions[READ])
+        if WRITE in permissions:
+            permissions['write'] = permissions[WRITE]
+            del(permissions[WRITE])
+        return permissions
     
     def access_clear(self, path):
         """Revoke access to path (both permissions and public)."""
@@ -79,13 +101,9 @@ class Permissions(XFeatures, Groups, Public):
     def access_check(self, path, access, member):
         """Return true if the member has this access to the path."""
         
-        if access == READ and self.public_get(path) is not None:
-            return True
-        
-        r = self.xfeature_inherit(path)
-        if not r:
+        feature = self.xfeature_get(path)
+        if not feature:
             return False
-        fpath, feature = r
         members = self.feature_get(feature, access)
         if member in members or '*' in members:
             return True
@@ -95,25 +113,20 @@ class Permissions(XFeatures, Groups, Public):
         return False
     
     def access_inherit(self, path):
-        """Return the inherited or assigned (path, permissions) pair for path."""
+        """Return the paths influencing the access for path."""
         
         r = self.xfeature_inherit(path)
         if not r:
-            return (path, {})
-        fpath, feature = r
-        permissions = self.feature_dict(feature)
-        if READ in permissions:
-            permissions['read'] = permissions[READ]
-            del(permissions[READ])
-        if WRITE in permissions:
-            permissions['write'] = permissions[WRITE]
-            del(permissions[WRITE])
-        return (fpath, permissions)
-    
-    def access_list(self, path):
-        """List all permission paths inherited by or inheriting from path."""
+            return []
         
-        return [x[0] for x in self.xfeature_list(path) if x[0] != path]
+        # Only keep path components.
+        parts = path.rstrip('/').split('/')
+        valid = []
+        for i in range(1, len(parts)):
+            subp = '/'.join(parts[:i + 1])
+            valid.append(subp)
+            valid.append(subp + '/')
+        return [x[0] for x in r if x[0] in valid]
     
     def access_list_paths(self, member, prefix=None):
         """Return the list of paths granted to member."""
index 8fca110..f015d5e 100644 (file)
@@ -72,48 +72,34 @@ class XFeatures(DBWorker):
         
         s = select([self.xfeatures.c.path, self.xfeatures.c.feature_id])
         s = s.where(self.xfeatures.c.path <= path)
-        s = s.order_by(desc(self.xfeatures.c.path)).limit(1)
+        #s = s.where(self.xfeatures.c.path.like(self.escape_like(path) + '%', escape='\\')) # XXX: Implement reverse and escape like...
+        s = s.order_by(desc(self.xfeatures.c.path))
         r = self.conn.execute(s)
-        row = r.fetchone()
+        l = r.fetchall()
         r.close()
-        if row and path.startswith(row[0]):
-            return row
-        else:
-            return None
+        return l
     
-    def xfeature_list(self, path):
-        """Return the list of the (prefix, feature) pairs matching path.
-           A prefix matches path if either the prefix includes the path,
-           or the path includes the prefix.
-        """
-        
-        inherited = self.xfeature_inherit(path)
-        if inherited:
-            return [inherited]
+    def xfeature_get(self, path):
+        """Return feature for path."""
         
-        s = select([self.xfeatures.c.path, self.xfeatures.c.feature_id])
-        s = s.where(and_(self.xfeatures.c.path.like(self.escape_like(path) + '%', escape='\\'),
-                     self.xfeatures.c.path != path))
+        s = select([self.xfeatures.c.feature_id])
+        s = s.where(self.xfeatures.c.path == path)
         s = s.order_by(self.xfeatures.c.path)
         r = self.conn.execute(s)
-        l = r.fetchall()
+        row = r.fetchone()
         r.close()
-        return l
+        if row:
+            return row[0]
+        return None
     
     def xfeature_create(self, path):
         """Create and return a feature for path.
-           If the path already inherits a feature or
-           bestows to paths already inheriting a feature,
-           create no feature and return None.
            If the path has a feature, return it.
         """
         
-        prefixes = self.xfeature_list(path)
-        pl = len(prefixes)
-        if (pl > 1) or (pl == 1 and prefixes[0][0] != path):
-            return None
-        if pl == 1 and prefixes[0][0] == path:
-            return prefixes[0][1]
+        feature = self.xfeature_get(path)
+        if feature is not None:
+            return feature
         s = self.xfeatures.insert()
         r = self.conn.execute(s, path=path)
         inserted_primary_key = r.inserted_primary_key[0]
index e69bf3f..b9f729e 100644 (file)
@@ -75,6 +75,8 @@ class Permissions(XFeatures, Groups, Public):
             self.feature_setmany(feature, WRITE, w)
     
     def access_get(self, path):
+        """Get permissions for path."""
+        
         feature = self.xfeature_get(path)
         if not feature:
             return {}
index e06cfab..cc71094 100644 (file)
@@ -71,21 +71,6 @@ class XFeatures(DBWorker):
         self.execute(q, (path, path))
         return self.fetchall()
     
-#     def xfeature_list(self, path):
-#         """Return the list of the (prefix, feature) pairs matching path.
-#            A prefix matches path if either the prefix includes the path,
-#            or the path includes the prefix.
-#         """
-#         
-#         inherited = self.xfeature_inherit(path)
-#         if inherited:
-#             return [inherited]
-#         
-#         q = ("select path, feature_id from xfeatures "
-#              "where path like ? escape '\\' and path != ? order by path")
-#         self.execute(q, (self.escape_like(path) + '%', path,))
-#         return self.fetchall()
-    
     def xfeature_get(self, path):
         """Return feature for path."""
         
@@ -94,6 +79,7 @@ class XFeatures(DBWorker):
         r = self.fetchone()
         if r is not None:
             return r[0]
+        return None
     
     def xfeature_create(self, path):
         """Create and return a feature for path.