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