Revision 6f4bce7b

b/pithos/backends/lib/groups.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from collections import defaultdict
35

  
34 36
from dbworker import DBWorker
35 37

  
36 38

  
......
56 58
        self.execute(q, (owner,))
57 59
        return [r[0] for r in self.fetchall()]
58 60
    
59
    def group_list(self, owner):
60
        """List all (group, member) tuples belonging to owner."""
61
    def group_dict(self, owner):
62
        """Return a dict mapping group names to member lists for owner."""
61 63
        
62 64
        q = "select name, member from groups where owner = ?"
63 65
        self.execute(q, (owner,))
64
        return self.fetchall()
66
        d = defaultdict(list)
67
        for group, member in self.fetchall():
68
            d[group].append(member)
69
        return d
65 70
    
66 71
    def group_add(self, owner, group, member):
67 72
        """Add a member to a group."""
b/pithos/backends/lib/hashfiler/blocker.py
36 36
from hashlib import new as newhasher
37 37
from binascii import hexlify
38 38

  
39
from pithos.lib.hashfiler.context_file import ContextFile, file_sync_read_chunks
39
from context_file import ContextFile, file_sync_read_chunks
40 40

  
41 41

  
42 42
class Blocker(object):
b/pithos/backends/lib/hashfiler/mapper.py
35 35
from os import makedirs, unlink
36 36
from errno import ENOENT
37 37

  
38
from pithos.lib.hashfiler.context_file import ContextFile
38
from context_file import ContextFile
39 39

  
40 40

  
41 41
class Mapper(object):
b/pithos/backends/lib/permissions.py
36 36
from public import Public
37 37

  
38 38

  
39
READ = 0
40
WRITE = 1
41

  
42

  
39 43
class Permissions(XFeatures, Groups, Public):
40 44
    
41 45
    def __init__(self, **params):
......
43 47
        Groups.__init__(self, **params)
44 48
        Public.__init__(self, **params)
45 49
    
46
    def access_grant(self, access, path, member='all', members=()):
47
        """Grant a member with an access to a path."""
48
        xfeatures = self.xfeature_list(path)
49
        xfl = len(xfeatures)
50
        if xfl > 1 or (xfl == 1 and xfeatures[0][0] != path):
51
            return xfeatures
52
        if xfl == 0:
53
            feature = self.alloc_serial()
54
            self.xfeature_bestow(path, feature)
55
        else:
56
            fpath, feature = xfeatures[0]
57

  
58
        if members:
59
            self.feature_setmany(feature, access, members)
60
        else:
61
            self.feature_set(feature, access, member)
62

  
63
        return ()
64

  
65
    def access_revoke(self, access, path, member='all', members=()):
66
        """Revoke access to path from members.
67
           Note that this will not revoke access for members
68
           that are indirectly granted access through group membership.
69
        """
70
        # XXX: Maybe provide a force_revoke that will kick out
71
        #      all groups containing the given members?
72
        xfeatures = self.xfeature_list(path)
73
        xfl = len(xfeatures)
74
        if xfl != 1 or xfeatures[0][0] != path:
75
            return xfeatures
76

  
77
        fpath, feature = xfeatures[0]
78

  
79
        if members:
80
            self.feature_unsetmany(feature, access, members=members)
81
        else:
82
            self.feature_unset(feature, access, member)
83

  
84
        # XXX: provide a meaningful return value? 
85

  
86
        return ()
87

  
88
    def access_check(self, access, path, member):
50
    def access_grant(self, path, access, members=()):
51
        """Grant members with access to path."""
52
        
53
        feature = self.xfeature_create(path)
54
        if feature is None:
55
            return
56
        self.feature_setmany(feature, access, members)
57
    
58
    def access_revoke_all(self, path):
59
        """Revoke access to path."""
60
        
61
        self.xfeature_destroy(path)
62
    
63
    def access_check(self, path, access, member):
89 64
        """Return true if the member has this access to the path."""
65
        
66
        if access == READ and self.public_check(path):
67
            return True
68
        
90 69
        r = self.xfeature_inherit(path)
91 70
        if not r:
92
            return 0
93

  
71
            return False
94 72
        fpath, feature = r
95
        memberset = set(self.feature_get(feature, access))
96
        if member in memberset:
97
            return 1
98

  
99
        for group in self.group_parents(self, member):
100
            if group in memberset:
101
                return 1
102

  
103
        return 0
104

  
105
    def access_list(self, path):
106
        """Return the list of (access, member) pairs for the path."""
73
        members = self.feature_get(feature, access)
74
        if member in members or '*' in members:
75
            return True
76
        for owner, group in self.group_parents(self, member):
77
            if owner + ':' + group in members:
78
                return True
79
        return True
80
    
81
    def access_inherit(self, path):
82
        """Return the inherited or assigned (path, permissions) pair for path."""
83
        
107 84
        r = self.xfeature_inherit(path)
108 85
        if not r:
109
            return ()
110

  
86
            return (path, {})
111 87
        fpath, feature = r
112
        return self.feature_list(feature)
113

  
114
    def access_list_paths(self, member):
115
        """Return the list of (access, path) pairs granted to member."""
116
        q = ("select distinct key, path from xfeatures inner join "
117
             "   (select distinct feature, key from xfeaturevals inner join "
118
             "      (select name as value from members "
88
        return (fpath, self.feature_dict(feature))
89
    
90
    def access_list(self, path):
91
        """List all permission paths inherited by or inheriting from path."""
92
        
93
        return [x[0] for x in self.xfeature_list(path) if x[0] != path]
94
    
95
    def access_list_paths(self, member, prefix=None):
96
        """Return the list of paths granted to member."""
97
        
98
        q = ("select distinct path from xfeatures inner join "
99
             "   (select distinct feature_id, key from xfeaturevals inner join "
100
             "      (select owner || ':' || name as value from members "
119 101
             "       where member = ? union select ?) "
120 102
             "    using (value)) "
121
             "using (feature)")
122

  
123
        self.execute(q, (member, member))
124
        return self.fetchall()
103
             "using (feature_id)")
104
        p = (member, member)
105
        if prefix:
106
            q += " where path like ?"
107
            p += (prefix + '%',)
108
        self.execute(q, p)
109
        return [r[0] for r in self.fetchall()]
110
    
111
    def access_list_shared(self, prefix=''):
112
        """Return the list of shared paths."""
113
        
114
        q = "select path from xfeatures where path like ?"
115
        self.execute(q, (prefix + '%',))
116
        return [r[0] for r in self.fetchall()]
b/pithos/backends/lib/xfeatures.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
from collections import defaultdict
35

  
34 36
from dbworker import DBWorker
35 37

  
36 38

  
......
91 93
           If the path already inherits a feature or
92 94
           bestows to paths already inheriting a feature,
93 95
           create no feature and return None.
96
           If the path has a feature, return it.
94 97
        """
95 98
        
96 99
        prefixes = self.xfeature_list(path)
97 100
        pl = len(prefixes)
98 101
        if (pl > 1) or (pl == 1 and prefixes[0][0] != path):
99 102
            return None
103
        if pl == 1 and prefixes[0][0] == path:
104
            return prefixes[0][1]
100 105
        q = "insert into xfeatures (path) values (?)"
101 106
        id = self.execute(q, (path,)).lastrowid
102 107
        return id
......
107 112
        q = "delete from xfeatures where path = ?"
108 113
        self.execute(q, (path,))
109 114
    
110
    def feature_list(self, feature):
111
        """Return the list of all key, value pairs
112
           associated with a feature.
113
        """
115
    def feature_dict(self, feature):
116
        """Return a dict mapping keys to list of values for feature."""
114 117
        
115 118
        q = "select key, value from xfeaturevals where feature = ?"
116 119
        self.execute(q, (feature,))
117
        return self.fetchall()
120
        d = defaultdict(list)
121
        for key, value in self.fetchall():
122
            d[key].append(value)
123
        return d
118 124
    
119 125
    def feature_set(self, feature, key, value):
120 126
        """Associate a key, value pair with a feature."""
b/pithos/backends/modular.py
39 39
import binascii
40 40

  
41 41
from base import NotAllowedError, BaseBackend
42
from lib.permissions import Permissions
42
from lib.permissions import Permissions, READ, WRITE
43 43
from lib.hashfiler import Mapper, Blocker
44 44

  
45 45

  
......
460 460
        if user != account:
461 461
            raise NotAllowedError
462 462
        path = self._get_objectinfo(account, container, name)[0]
463
        r, w = self._check_permissions(path, permissions)
464
        self._put_permissions(path, r, w)
463
        self._check_permissions(path, permissions)
464
        self._put_permissions(path, permissions)
465 465
    
466 466
    @backend_method
467 467
    def get_object_public(self, user, account, container, name):
......
509 509
        path = self._get_containerinfo(account, container)[0]
510 510
        path = '/'.join((path, name))
511 511
        if permissions is not None:
512
            r, w = self._check_permissions(path, permissions)
512
            self._check_permissions(path, permissions)
513 513
        src_version_id, dest_version_id = self._copy_version(user, path, path, not replace_meta, False)
514 514
        sql = 'update versions set size = ? where version_id = ?'
515 515
        self.con.execute(sql, (size, dest_version_id))
......
518 518
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
519 519
            self.con.execute(sql, (dest_version_id, k, v))
520 520
        if permissions is not None:
521
            self._put_permissions(path, r, w)
521
            self._put_permissions(path, permissions)
522 522
    
523 523
    @backend_method
524 524
    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 537
        dest_path = self._get_containerinfo(account, dest_container)[0]
538 538
        dest_path = '/'.join((dest_path, dest_name))
539 539
        if permissions is not None:
540
            r, w = self._check_permissions(dest_path, permissions)
540
            self._check_permissions(dest_path, permissions)
541 541
        src_version_id, dest_version_id = self._copy_version(user, src_path, dest_path, not replace_meta, True, src_version)
542 542
        for k, v in dest_meta.iteritems():
543 543
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
544 544
            self.con.execute(sql, (dest_version_id, k, v))
545 545
        if permissions is not None:
546
            self._put_permissions(dest_path, r, w)
546
            self._put_permissions(dest_path, permissions)
547 547
    
548 548
    @backend_method
549 549
    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
......
836 836
        pass
837 837
    
838 838
    def _get_groups(self, account):
839
        groups = {}
840
        for row in self.permissions.group_list(account):
841
            if group not in groups:
842
                groups[group] = []
843
            groups[group].append(member)
844
        return groups
839
        return self.permissions.group_dict(account)
845 840
    
846 841
    def _put_groups(self, account, groups, replace=False):
847 842
        if replace:
......
855 850
    def _del_groups(self, account):
856 851
        self.permissions.group_destroy(account)
857 852
    
858
    # ---------------- UP TO HERE ----------------
859
    
860 853
    def _check_permissions(self, path, permissions):
861 854
        # Check for existing permissions.
862
        sql = '''select name from permissions
863
                    where name != ? and (name like ? or ? like name || ?)'''
864
        c = self.con.execute(sql, (path, path + '%', path, '%'))
865
        row = c.fetchone()
866
        if row:
855
        paths = self.permissions.access_list(path)
856
        if paths:
867 857
            ae = AttributeError()
868
            ae.data = row[0]
858
            ae.data = paths
869 859
            raise ae
870 860
        
871
        # Format given permissions.
872
        if len(permissions) == 0:
873
            return [], []
874
        r = permissions.get('read', [])
875
        w = permissions.get('write', [])
876 861
        # Examples follow.
877 862
        # if True in [False or ',' in x for x in r]:
878 863
        #     raise ValueError('Bad characters in read permissions')
879 864
        # if True in [False or ',' in x for x in w]:
880 865
        #     raise ValueError('Bad characters in write permissions')
881
        return r, w
866
        pass
882 867
    
883 868
    def _get_permissions(self, path):
884
        # Check for permissions at path or above.
885
        sql = 'select name, op, user from permissions where ? like name || ?'
886
        c = self.con.execute(sql, (path, '%'))
887
        name = path
888
        perms = {} # Return nothing, if nothing is set.
889
        for row in c.fetchall():
890
            name = row[0]
891
            if row[1] not in perms:
892
                perms[row[1]] = []
893
            perms[row[1]].append(row[2])
894
        return name, perms
895
    
896
    def _put_permissions(self, path, r, w):
897
        sql = 'delete from permissions where name = ?'
898
        self.con.execute(sql, (path,))
899
        sql = 'insert into permissions (name, op, user) values (?, ?, ?)'
869
        self.permissions.access_inherit(path)
870
    
871
    def _put_permissions(self, path, permissions):
872
        self.permissions.access_revoke_all(path)
873
        r = permissions.get('read', [])
900 874
        if r:
901
            self.con.executemany(sql, [(path, 'read', x) for x in r])
875
            self.permissions.access_grant(path, READ, r)
876
        w = permissions.get('write', [])
902 877
        if w:
903
            self.con.executemany(sql, [(path, 'write', x) for x in w])
878
            self.permissions.access_grant(path, WRITE, w)
904 879
    
905 880
    def _get_public(self, path):
906
        sql = 'select name from public where name = ?'
907
        c = self.con.execute(sql, (path,))
908
        row = c.fetchone()
909
        if not row:
910
            return False
911
        return True
881
        return self.permissions.public_check(path)
912 882
    
913 883
    def _put_public(self, path, public):
914 884
        if not public:
915
            sql = 'delete from public where name = ?'
885
            self.permissions.public_unset(path)
916 886
        else:
917
            sql = 'insert or replace into public (name) values (?)'
918
        self.con.execute(sql, (path,))
887
            self.permissions.public_set(path)
919 888
    
920 889
    def _del_sharing(self, path):
921
        sql = 'delete from permissions where name = ?'
922
        self.con.execute(sql, (path,))
923
        sql = 'delete from public where name = ?'
924
        self.con.execute(sql, (path,))
890
        self.permissions.access_revoke_all(path)
891
        self.permissions.public_unset(path)
925 892
    
926
    def _is_allowed(self, user, account, container, name, op='read'):
893
    def _can_read(self, user, account, container, name):
927 894
        if user == account:
928 895
            return True
929 896
        path = '/'.join((account, container, name))
930
        if op == 'read' and self._get_public(path):
931
            return True
932
        perm_path, perms = self._get_permissions(path)
933
        
934
        # Expand groups.
935
        for x in ('read', 'write'):
936
            g_perms = set()
937
            for y in perms.get(x, []):
938
                if ':' in y:
939
                    g_account, g_name = y.split(':', 1)
940
                    groups = self._get_groups(g_account)
941
                    if g_name in groups:
942
                        g_perms.update(groups[g_name])
943
                else:
944
                    g_perms.add(y)
945
            perms[x] = g_perms
946
        
947
        if op == 'read' and ('*' in perms['read'] or user in perms['read']):
948
            return True
949
        if '*' in perms['write'] or user in perms['write']:
950
            return True
951
        return False
952
    
953
    def _can_read(self, user, account, container, name):
954
        if not self._is_allowed(user, account, container, name, 'read'):
897
        if not self.permissions.access_check(path, READ, user) and not self.permissions.access_check(path, WRITE, user):
955 898
            raise NotAllowedError
956 899
    
957 900
    def _can_write(self, user, account, container, name):
958
        if not self._is_allowed(user, account, container, name, 'write'):
901
        if user == account:
902
            return True
903
        path = '/'.join((account, container, name))
904
        if not self.permissions.access_check(path, WRITE, user):
959 905
            raise NotAllowedError
960 906
    
961 907
    def _allowed_paths(self, user, prefix=None):
962
        sql = '''select distinct name from permissions where (user = ?
963
                    or user in (select account || ':' || gname from groups where user = ?))'''
964
        param = (user, user)
965 908
        if prefix:
966
            sql += ' and name like ?'
967
            param += (prefix + '/%',)
968
        c = self.con.execute(sql, param)
969
        return [x[0] for x in c.fetchall()]
909
            prefix += '/'
910
        return self.permissions.access_list_paths(user, prefix)
970 911
    
971 912
    def _allowed_accounts(self, user):
972 913
        allow = set()
......
981 922
        return sorted(allow)
982 923
    
983 924
    def _shared_paths(self, prefix):
984
        sql = 'select distinct name from permissions where name like ?'
985
        c = self.con.execute(sql, (prefix + '/%',))
986
        return [x[0] for x in c.fetchall()]
925
        prefix += '/'
926
        return self.permissions.access_list_shared(prefix)

Also available in: Unified diff