More modular backend.
authorAntony Chazapis <chazapis@gmail.com>
Thu, 28 Jul 2011 10:07:36 +0000 (13:07 +0300)
committerAntony Chazapis <chazapis@gmail.com>
Thu, 28 Jul 2011 10:07:36 +0000 (13:07 +0300)
pithos/backends/lib/__init__.py [new file with mode: 0644]
pithos/backends/lib/groups.py
pithos/backends/lib/hashfiler/blocker.py
pithos/backends/lib/hashfiler/mapper.py
pithos/backends/lib/permissions.py
pithos/backends/lib/xfeatures.py
pithos/backends/modular.py

diff --git a/pithos/backends/lib/__init__.py b/pithos/backends/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
index dfdacbd..8ae9746 100644 (file)
@@ -31,6 +31,8 @@
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+from collections import defaultdict
+
 from dbworker import DBWorker
 
 
@@ -56,12 +58,15 @@ class Groups(DBWorker):
         self.execute(q, (owner,))
         return [r[0] for r in self.fetchall()]
     
-    def group_list(self, owner):
-        """List all (group, member) tuples belonging to owner."""
+    def group_dict(self, owner):
+        """Return a dict mapping group names to member lists for owner."""
         
         q = "select name, member from groups where owner = ?"
         self.execute(q, (owner,))
-        return self.fetchall()
+        d = defaultdict(list)
+        for group, member in self.fetchall():
+            d[group].append(member)
+        return d
     
     def group_add(self, owner, group, member):
         """Add a member to a group."""
index 6010009..e326b1b 100644 (file)
@@ -36,7 +36,7 @@ from os.path import isdir, realpath, exists, join
 from hashlib import new as newhasher
 from binascii import hexlify
 
-from pithos.lib.hashfiler.context_file import ContextFile, file_sync_read_chunks
+from context_file import ContextFile, file_sync_read_chunks
 
 
 class Blocker(object):
index eebfd69..825bc9c 100644 (file)
@@ -35,7 +35,7 @@ from os.path import realpath, join, exists, isdir
 from os import makedirs, unlink
 from errno import ENOENT
 
-from pithos.lib.hashfiler.context_file import ContextFile
+from context_file import ContextFile
 
 
 class Mapper(object):
index 24a79c8..c09e4c8 100644 (file)
@@ -36,6 +36,10 @@ from groups import Groups
 from public import Public
 
 
+READ = 0
+WRITE = 1
+
+
 class Permissions(XFeatures, Groups, Public):
     
     def __init__(self, **params):
@@ -43,82 +47,70 @@ class Permissions(XFeatures, Groups, Public):
         Groups.__init__(self, **params)
         Public.__init__(self, **params)
     
-    def access_grant(self, access, path, member='all', members=()):
-        """Grant a member with an access to a path."""
-        xfeatures = self.xfeature_list(path)
-        xfl = len(xfeatures)
-        if xfl > 1 or (xfl == 1 and xfeatures[0][0] != path):
-            return xfeatures
-        if xfl == 0:
-            feature = self.alloc_serial()
-            self.xfeature_bestow(path, feature)
-        else:
-            fpath, feature = xfeatures[0]
-
-        if members:
-            self.feature_setmany(feature, access, members)
-        else:
-            self.feature_set(feature, access, member)
-
-        return ()
-
-    def access_revoke(self, access, path, member='all', members=()):
-        """Revoke access to path from members.
-           Note that this will not revoke access for members
-           that are indirectly granted access through group membership.
-        """
-        # XXX: Maybe provide a force_revoke that will kick out
-        #      all groups containing the given members?
-        xfeatures = self.xfeature_list(path)
-        xfl = len(xfeatures)
-        if xfl != 1 or xfeatures[0][0] != path:
-            return xfeatures
-
-        fpath, feature = xfeatures[0]
-
-        if members:
-            self.feature_unsetmany(feature, access, members=members)
-        else:
-            self.feature_unset(feature, access, member)
-
-        # XXX: provide a meaningful return value? 
-
-        return ()
-
-    def access_check(self, access, path, member):
+    def access_grant(self, path, access, members=()):
+        """Grant members with access to path."""
+        
+        feature = self.xfeature_create(path)
+        if feature is None:
+            return
+        self.feature_setmany(feature, access, members)
+    
+    def access_revoke_all(self, path):
+        """Revoke access to path."""
+        
+        self.xfeature_destroy(path)
+    
+    def access_check(self, path, access, member):
         """Return true if the member has this access to the path."""
+        
+        if access == READ and self.public_check(path):
+            return True
+        
         r = self.xfeature_inherit(path)
         if not r:
-            return 0
-
+            return False
         fpath, feature = r
-        memberset = set(self.feature_get(feature, access))
-        if member in memberset:
-            return 1
-
-        for group in self.group_parents(self, member):
-            if group in memberset:
-                return 1
-
-        return 0
-
-    def access_list(self, path):
-        """Return the list of (access, member) pairs for the path."""
+        members = self.feature_get(feature, access)
+        if member in members or '*' in members:
+            return True
+        for owner, group in self.group_parents(self, member):
+            if owner + ':' + group in members:
+                return True
+        return True
+    
+    def access_inherit(self, path):
+        """Return the inherited or assigned (path, permissions) pair for path."""
+        
         r = self.xfeature_inherit(path)
         if not r:
-            return ()
-
+            return (path, {})
         fpath, feature = r
-        return self.feature_list(feature)
-
-    def access_list_paths(self, member):
-        """Return the list of (access, path) pairs granted to member."""
-        q = ("select distinct key, path from xfeatures inner join "
-             "   (select distinct feature, key from xfeaturevals inner join "
-             "      (select name as value from members "
+        return (fpath, self.feature_dict(feature))
+    
+    def access_list(self, path):
+        """List all permission paths inherited by or inheriting from path."""
+        
+        return [x[0] for x in self.xfeature_list(path) if x[0] != path]
+    
+    def access_list_paths(self, member, prefix=None):
+        """Return the list of paths granted to member."""
+        
+        q = ("select distinct path from xfeatures inner join "
+             "   (select distinct feature_id, key from xfeaturevals inner join "
+             "      (select owner || ':' || name as value from members "
              "       where member = ? union select ?) "
              "    using (value)) "
-             "using (feature)")
-
-        self.execute(q, (member, member))
-        return self.fetchall()
+             "using (feature_id)")
+        p = (member, member)
+        if prefix:
+            q += " where path like ?"
+            p += (prefix + '%',)
+        self.execute(q, p)
+        return [r[0] for r in self.fetchall()]
+    
+    def access_list_shared(self, prefix=''):
+        """Return the list of shared paths."""
+        
+        q = "select path from xfeatures where path like ?"
+        self.execute(q, (prefix + '%',))
+        return [r[0] for r in self.fetchall()]
index 99cf6ea..3fa8b90 100644 (file)
@@ -31,6 +31,8 @@
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+from collections import defaultdict
+
 from dbworker import DBWorker
 
 
@@ -91,12 +93,15 @@ class XFeatures(DBWorker):
            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]
         q = "insert into xfeatures (path) values (?)"
         id = self.execute(q, (path,)).lastrowid
         return id
@@ -107,14 +112,15 @@ class XFeatures(DBWorker):
         q = "delete from xfeatures where path = ?"
         self.execute(q, (path,))
     
-    def feature_list(self, feature):
-        """Return the list of all key, value pairs
-           associated with a feature.
-        """
+    def feature_dict(self, feature):
+        """Return a dict mapping keys to list of values for feature."""
         
         q = "select key, value from xfeaturevals where feature = ?"
         self.execute(q, (feature,))
-        return self.fetchall()
+        d = defaultdict(list)
+        for key, value in self.fetchall():
+            d[key].append(value)
+        return d
     
     def feature_set(self, feature, key, value):
         """Associate a key, value pair with a feature."""
index 25456f1..773b507 100644 (file)
@@ -39,7 +39,7 @@ import hashlib
 import binascii
 
 from base import NotAllowedError, BaseBackend
-from lib.permissions import Permissions
+from lib.permissions import Permissions, READ, WRITE
 from lib.hashfiler import Mapper, Blocker
 
 
@@ -460,8 +460,8 @@ class ModularBackend(BaseBackend):
         if user != account:
             raise NotAllowedError
         path = self._get_objectinfo(account, container, name)[0]
-        r, w = self._check_permissions(path, permissions)
-        self._put_permissions(path, r, w)
+        self._check_permissions(path, permissions)
+        self._put_permissions(path, permissions)
     
     @backend_method
     def get_object_public(self, user, account, container, name):
@@ -509,7 +509,7 @@ class ModularBackend(BaseBackend):
         path = self._get_containerinfo(account, container)[0]
         path = '/'.join((path, name))
         if permissions is not None:
-            r, w = self._check_permissions(path, permissions)
+            self._check_permissions(path, permissions)
         src_version_id, dest_version_id = self._copy_version(user, path, path, not replace_meta, False)
         sql = 'update versions set size = ? where version_id = ?'
         self.con.execute(sql, (size, dest_version_id))
@@ -518,7 +518,7 @@ class ModularBackend(BaseBackend):
             sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
             self.con.execute(sql, (dest_version_id, k, v))
         if permissions is not None:
-            self._put_permissions(path, r, w)
+            self._put_permissions(path, permissions)
     
     @backend_method
     def copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
@@ -537,13 +537,13 @@ class ModularBackend(BaseBackend):
         dest_path = self._get_containerinfo(account, dest_container)[0]
         dest_path = '/'.join((dest_path, dest_name))
         if permissions is not None:
-            r, w = self._check_permissions(dest_path, permissions)
+            self._check_permissions(dest_path, permissions)
         src_version_id, dest_version_id = self._copy_version(user, src_path, dest_path, not replace_meta, True, src_version)
         for k, v in dest_meta.iteritems():
             sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
             self.con.execute(sql, (dest_version_id, k, v))
         if permissions is not None:
-            self._put_permissions(dest_path, r, w)
+            self._put_permissions(dest_path, permissions)
     
     @backend_method
     def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
@@ -836,12 +836,7 @@ class ModularBackend(BaseBackend):
         pass
     
     def _get_groups(self, account):
-        groups = {}
-        for row in self.permissions.group_list(account):
-            if group not in groups:
-                groups[group] = []
-            groups[group].append(member)
-        return groups
+        return self.permissions.group_dict(account)
     
     def _put_groups(self, account, groups, replace=False):
         if replace:
@@ -855,118 +850,64 @@ class ModularBackend(BaseBackend):
     def _del_groups(self, account):
         self.permissions.group_destroy(account)
     
-    # ---------------- UP TO HERE ----------------
-    
     def _check_permissions(self, path, permissions):
         # Check for existing permissions.
-        sql = '''select name from permissions
-                    where name != ? and (name like ? or ? like name || ?)'''
-        c = self.con.execute(sql, (path, path + '%', path, '%'))
-        row = c.fetchone()
-        if row:
+        paths = self.permissions.access_list(path)
+        if paths:
             ae = AttributeError()
-            ae.data = row[0]
+            ae.data = paths
             raise ae
         
-        # Format given permissions.
-        if len(permissions) == 0:
-            return [], []
-        r = permissions.get('read', [])
-        w = permissions.get('write', [])
         # Examples follow.
         # if True in [False or ',' in x for x in r]:
         #     raise ValueError('Bad characters in read permissions')
         # if True in [False or ',' in x for x in w]:
         #     raise ValueError('Bad characters in write permissions')
-        return r, w
+        pass
     
     def _get_permissions(self, path):
-        # Check for permissions at path or above.
-        sql = 'select name, op, user from permissions where ? like name || ?'
-        c = self.con.execute(sql, (path, '%'))
-        name = path
-        perms = {} # Return nothing, if nothing is set.
-        for row in c.fetchall():
-            name = row[0]
-            if row[1] not in perms:
-                perms[row[1]] = []
-            perms[row[1]].append(row[2])
-        return name, perms
-    
-    def _put_permissions(self, path, r, w):
-        sql = 'delete from permissions where name = ?'
-        self.con.execute(sql, (path,))
-        sql = 'insert into permissions (name, op, user) values (?, ?, ?)'
+        self.permissions.access_inherit(path)
+    
+    def _put_permissions(self, path, permissions):
+        self.permissions.access_revoke_all(path)
+        r = permissions.get('read', [])
         if r:
-            self.con.executemany(sql, [(path, 'read', x) for x in r])
+            self.permissions.access_grant(path, READ, r)
+        w = permissions.get('write', [])
         if w:
-            self.con.executemany(sql, [(path, 'write', x) for x in w])
+            self.permissions.access_grant(path, WRITE, w)
     
     def _get_public(self, path):
-        sql = 'select name from public where name = ?'
-        c = self.con.execute(sql, (path,))
-        row = c.fetchone()
-        if not row:
-            return False
-        return True
+        return self.permissions.public_check(path)
     
     def _put_public(self, path, public):
         if not public:
-            sql = 'delete from public where name = ?'
+            self.permissions.public_unset(path)
         else:
-            sql = 'insert or replace into public (name) values (?)'
-        self.con.execute(sql, (path,))
+            self.permissions.public_set(path)
     
     def _del_sharing(self, path):
-        sql = 'delete from permissions where name = ?'
-        self.con.execute(sql, (path,))
-        sql = 'delete from public where name = ?'
-        self.con.execute(sql, (path,))
+        self.permissions.access_revoke_all(path)
+        self.permissions.public_unset(path)
     
-    def _is_allowed(self, user, account, container, name, op='read'):
+    def _can_read(self, user, account, container, name):
         if user == account:
             return True
         path = '/'.join((account, container, name))
-        if op == 'read' and self._get_public(path):
-            return True
-        perm_path, perms = self._get_permissions(path)
-        
-        # Expand groups.
-        for x in ('read', 'write'):
-            g_perms = set()
-            for y in perms.get(x, []):
-                if ':' in y:
-                    g_account, g_name = y.split(':', 1)
-                    groups = self._get_groups(g_account)
-                    if g_name in groups:
-                        g_perms.update(groups[g_name])
-                else:
-                    g_perms.add(y)
-            perms[x] = g_perms
-        
-        if op == 'read' and ('*' in perms['read'] or user in perms['read']):
-            return True
-        if '*' in perms['write'] or user in perms['write']:
-            return True
-        return False
-    
-    def _can_read(self, user, account, container, name):
-        if not self._is_allowed(user, account, container, name, 'read'):
+        if not self.permissions.access_check(path, READ, user) and not self.permissions.access_check(path, WRITE, user):
             raise NotAllowedError
     
     def _can_write(self, user, account, container, name):
-        if not self._is_allowed(user, account, container, name, 'write'):
+        if user == account:
+            return True
+        path = '/'.join((account, container, name))
+        if not self.permissions.access_check(path, WRITE, user):
             raise NotAllowedError
     
     def _allowed_paths(self, user, prefix=None):
-        sql = '''select distinct name from permissions where (user = ?
-                    or user in (select account || ':' || gname from groups where user = ?))'''
-        param = (user, user)
         if prefix:
-            sql += ' and name like ?'
-            param += (prefix + '/%',)
-        c = self.con.execute(sql, param)
-        return [x[0] for x in c.fetchall()]
+            prefix += '/'
+        return self.permissions.access_list_paths(user, prefix)
     
     def _allowed_accounts(self, user):
         allow = set()
@@ -981,6 +922,5 @@ class ModularBackend(BaseBackend):
         return sorted(allow)
     
     def _shared_paths(self, prefix):
-        sql = 'select distinct name from permissions where name like ?'
-        c = self.con.execute(sql, (prefix + '/%',))
-        return [x[0] for x in c.fetchall()]
+        prefix += '/'
+        return self.permissions.access_list_shared(prefix)