Revision 0f9d752c

b/pithos/backends/__init__.py
34 34
from django.conf import settings
35 35

  
36 36
from simple import SimpleBackend
37
#from modular import ModularBackend
37
from modular import ModularBackend
38 38

  
39 39
backend = None
40 40
options = getattr(settings, 'BACKEND', None)
b/pithos/backends/lib/permissions.py
48 48
        Public.__init__(self, **params)
49 49
    
50 50
    def access_grant(self, path, access, members=()):
51
        """Grant members with access to path."""
51
        """Grant members with access to path.
52
           Members can also be '*' (all),
53
           or some group specified as 'owner:group'."""
52 54
        
55
        if not members:
56
            return
53 57
        feature = self.xfeature_create(path)
54 58
        if feature is None:
55 59
            return
56 60
        self.feature_setmany(feature, access, members)
57 61
    
58
    def access_revoke_all(self, path):
59
        """Revoke access to path."""
62
    def access_set(self, path, permissions):
63
        """Set permissions for path. The permissions dict
64
           maps 'read', 'write' keys to member lists."""
65
        
66
        self.xfeature_destroy(path)
67
        self.access_grant(path, READ, permissions.get('read', []))
68
        self.access_grant(path, WRITE, permissions.get('write', []))
69
    
70
    def access_clear(self, path):
71
        """Revoke access to path (both permissions and public)."""
60 72
        
61 73
        self.xfeature_destroy(path)
74
        self.public_unset(path)
62 75
    
63 76
    def access_check(self, path, access, member):
64 77
        """Return true if the member has this access to the path."""
......
85 98
        if not r:
86 99
            return (path, {})
87 100
        fpath, feature = r
88
        return (fpath, self.feature_dict(feature))
101
        permissions = self.feature_dict(feature)
102
        if READ in permissions:
103
            permissions['read'] = permissions[READ]
104
            del(permissions[READ])
105
        if WRITE in permissions:
106
            permissions['write'] = permissions[WRITE]
107
            del(permissions[WRITE])
108
        return (fpath, permissions)
89 109
    
90 110
    def access_list(self, path):
91 111
        """List all permission paths inherited by or inheriting from path."""
......
97 117
        
98 118
        q = ("select distinct path from xfeatures inner join "
99 119
             "   (select distinct feature_id, key from xfeaturevals inner join "
100
             "      (select owner || ':' || name as value from members "
120
             "      (select owner || ':' || name as value from groups "
101 121
             "       where member = ? union select ?) "
102 122
             "    using (value)) "
103 123
             "using (feature_id)")
b/pithos/backends/lib/policy.py
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
# 
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
# 
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
# 
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
# 
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from dbworker import DBWorker
35

  
36

  
37
class Policy(DBWorker):
38
    """Paths can be assigned key, value pairs, representing policy."""
39
    
40
    def __init__(self, **params):
41
        DBWorker.__init__(self, **params)
42
        execute = self.execute
43
        
44
        execute(""" create table if not exists policy
45
                          ( path  text,
46
                            key   text,
47
                            value text,
48
                            primary key (path, key) ) """)
49
    
50
    def policy_set(self, path, policy):
51
        q = "insert or replace into policy (path, key, value) values (?, ?, ?)"
52
        self.executemany(q, ((path, k, v) for k, v in policy.iteritems()))
53
    
54
    def policy_unset(self, path):
55
        q = "delete from policy where path = ?"
56
        self.execute(q, (path,))
57
    
58
    def policy_get(self, path):
59
        q = "select key, value from policy where path = ?"
60
        self.execute(q, (path,))
61
        return dict(self.fetchall())
b/pithos/backends/modular.py
40 40

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

  
45 46

  
......
106 107
                    foreign key (version_id) references versions(version_id)
107 108
                    on delete cascade)'''
108 109
        self.con.execute(sql)
109
        sql = '''create table if not exists policy (
110
                    name text, key text, value text, primary key (name, key))'''
111
        self.con.execute(sql)
112
                
110
        
113 111
        self.con.commit()
114 112
        
115 113
        params = {'blocksize': self.block_size,
......
124 122
        params = {'connection': self.con,
125 123
                  'cursor': self.con.cursor()}
126 124
        self.permissions = Permissions(**params)
125
        self.policy = Policy(**params)
127 126
    
128 127
    @backend_method
129 128
    def list_accounts(self, user, marker=None, limit=10000):
......
195 194
                raise NotAllowedError
196 195
            return {}
197 196
        self._create_account(user, account)
198
        return self._get_groups(account)
197
        return self.permissions.group_dict(account)
199 198
    
200 199
    @backend_method
201 200
    def update_account_groups(self, user, account, groups, replace=False):
......
206 205
            raise NotAllowedError
207 206
        self._create_account(user, account)
208 207
        self._check_groups(groups)
209
        self._put_groups(account, groups, replace)
208
        if replace:
209
            self.permissions.group_destroy(account)
210
        for k, v in groups.iteritems():
211
            if not replace: # If not already deleted.
212
                self.permissions.group_delete(account, k)
213
            if v:
214
                self.permissions.group_addmany(account, k, v)
210 215
    
211 216
    @backend_method
212 217
    def put_account(self, user, account):
......
235 240
            raise IndexError('Account is not empty')
236 241
        sql = 'delete from versions where name = ?'
237 242
        self.con.execute(sql, (account,))
238
        self._del_groups(account)
243
        self.permissions.group_destroy(account)
239 244
    
240 245
    @backend_method
241 246
    def list_containers(self, user, account, marker=None, limit=10000, shared=False, until=None):
......
250 255
            return allowed[start:start + limit]
251 256
        else:
252 257
            if shared:
253
                allowed = [x.split('/', 2)[1] for x in self._shared_paths(account)]
258
                allowed = [x.split('/', 2)[1] for x in self.permissions.access_list_shared(account)]
254 259
                start, limit = self._list_limits(allowed, marker, limit)
255 260
                return allowed[start:start + limit]
256 261
        return [x[0] for x in self._list_objects(account, '', '/', marker, limit, False, [], until)]
......
318 323
            for k, v in self.default_policy.iteritems():
319 324
                if k not in policy:
320 325
                    policy[k] = v
321
        for k, v in policy.iteritems():
322
            sql = 'insert or replace into policy (name, key, value) values (?, ?, ?)'
323
            self.con.execute(sql, (path, k, v))
326
        self.policy.policy_set(path, policy)
324 327
    
325 328
    @backend_method
326 329
    def put_container(self, user, account, container, policy=None):
......
342 345
        for k, v in self.default_policy.iteritems():
343 346
            if k not in policy:
344 347
                policy[k] = v
345
        for k, v in policy.iteritems():
346
            sql = 'insert or replace into policy (name, key, value) values (?, ?, ?)'
347
            self.con.execute(sql, (path, k, v))
348
        self.policy.policy_set(path, policy)
348 349
    
349 350
    @backend_method
350 351
    def delete_container(self, user, account, container, until=None):
......
369 370
            raise IndexError('Container is not empty')
370 371
        sql = 'delete from versions where name = ? or name like ?' # May contain hidden items.
371 372
        self.con.execute(sql, (path, path + '/%',))
372
        sql = 'delete from policy where name = ?'
373
        self.con.execute(sql, (path,))
373
        self.policy.policy_unset(path)
374 374
        self._copy_version(user, account, account, True, False) # New account version (for timestamp update).
375 375
    
376 376
    @backend_method
......
382 382
        if user != account:
383 383
            if until:
384 384
                raise NotAllowedError
385
            allowed = self._allowed_paths(user, '/'.join((account, container)))
385
            allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
386 386
            if not allowed:
387 387
                raise NotAllowedError
388 388
        else:
389 389
            if shared:
390
                allowed = self._shared_paths('/'.join((account, container)))
390
                allowed = self.permissions.access_list_shared('/'.join((account, container)))
391 391
        path, version_id, mtime = self._get_containerinfo(account, container, until)
392 392
        return self._list_objects(path, prefix, delimiter, marker, limit, virtual, keys, until, allowed)
393 393
    
......
400 400
        if user != account:
401 401
            if until:
402 402
                raise NotAllowedError
403
            allowed = self._allowed_paths(user, '/'.join((account, container)))
403
            allowed = self.permissions.access_list_paths(user, '/'.join((account, container)))
404 404
            if not allowed:
405 405
                raise NotAllowedError
406 406
        path, version_id, mtime = self._get_containerinfo(account, container, until)
......
450 450
        logger.debug("get_object_permissions: %s %s %s", account, container, name)
451 451
        self._can_read(user, account, container, name)
452 452
        path = self._get_objectinfo(account, container, name)[0]
453
        return self._get_permissions(path)
453
        return self.permissions.access_inherit(path)
454 454
    
455 455
    @backend_method
456 456
    def update_object_permissions(self, user, account, container, name, permissions):
......
461 461
            raise NotAllowedError
462 462
        path = self._get_objectinfo(account, container, name)[0]
463 463
        self._check_permissions(path, permissions)
464
        self._put_permissions(path, permissions)
464
        self.permissions.access_set(path, permissions)
465 465
    
466 466
    @backend_method
467 467
    def get_object_public(self, user, account, container, name):
......
470 470
        logger.debug("get_object_public: %s %s %s", account, container, name)
471 471
        self._can_read(user, account, container, name)
472 472
        path = self._get_objectinfo(account, container, name)[0]
473
        if self._get_public(path):
473
        if self.permissions.public_check(path):
474 474
            return '/public/' + path
475 475
        return None
476 476
    
......
481 481
        logger.debug("update_object_public: %s %s %s %s", account, container, name, public)
482 482
        self._can_write(user, account, container, name)
483 483
        path = self._get_objectinfo(account, container, name)[0]
484
        self._put_public(path, public)
484
        if not public:
485
            self.permissions.public_unset(path)
486
        else:
487
            self.permissions.public_set(path)
485 488
    
486 489
    @backend_method
487 490
    def get_object_hashmap(self, user, account, container, name, version=None):
......
518 521
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
519 522
            self.con.execute(sql, (dest_version_id, k, v))
520 523
        if permissions is not None:
521
            self._put_permissions(path, permissions)
524
            self.permissions.access_set(path, permissions)
522 525
    
523 526
    @backend_method
524 527
    def copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
......
543 546
            sql = 'insert or replace into metadata (version_id, key, value) values (?, ?, ?)'
544 547
            self.con.execute(sql, (dest_version_id, k, v))
545 548
        if permissions is not None:
546
            self._put_permissions(dest_path, permissions)
549
            self.permissions.access_set(dest_path, permissions)
547 550
    
548 551
    @backend_method
549 552
    def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
......
572 575
            except NameError:
573 576
                pass
574 577
            else:
575
                self._del_sharing(path)
578
                self.permissions.access_clear(path)
576 579
            return
577 580
        
578 581
        path = self._get_objectinfo(account, container, name)[0]
579 582
        self._put_version(path, user, 0, 1)
580
        self._del_sharing(path)
583
        self.permissions.access_clear(path)
581 584
    
582 585
    @backend_method
583 586
    def list_versions(self, user, account, container, name):
......
761 764
                raise ValueError
762 765
    
763 766
    def _get_policy(self, path):
764
        sql = 'select key, value from policy where name = ?'
765
        c = self.con.execute(sql, (path,))
766
        return dict(c.fetchall())
767
        return self.policy.policy_get(path)
767 768
    
768 769
    def _list_limits(self, listing, marker, limit):
769 770
        start = 0
......
829 830
    # Access control functions.
830 831
    
831 832
    def _check_groups(self, groups):
832
        # Example follows.
833
        # for k, v in groups.iteritems():
834
        #     if True in [False or ',' in x for x in v]:
835
        #         raise ValueError('Bad characters in groups')
833
        # raise ValueError('Bad characters in groups')
836 834
        pass
837 835
    
838
    def _get_groups(self, account):
839
        return self.permissions.group_dict(account)
840
    
841
    def _put_groups(self, account, groups, replace=False):
842
        if replace:
843
            self.permissions.group_destroy(account)
844
        for k, v in groups.iteritems():
845
            if not replace: # If not already deleted.
846
                self.permissions.group_delete(account, k)
847
            if v:
848
                self.permissions.group_addmany(account, k, v)
849
    
850
    def _del_groups(self, account):
851
        self.permissions.group_destroy(account)
852
    
853 836
    def _check_permissions(self, path, permissions):
837
        # raise ValueError('Bad characters in permissions')
838
        
854 839
        # Check for existing permissions.
855 840
        paths = self.permissions.access_list(path)
856 841
        if paths:
857 842
            ae = AttributeError()
858 843
            ae.data = paths
859 844
            raise ae
860
        
861
        # Examples follow.
862
        # if True in [False or ',' in x for x in r]:
863
        #     raise ValueError('Bad characters in read permissions')
864
        # if True in [False or ',' in x for x in w]:
865
        #     raise ValueError('Bad characters in write permissions')
866
        pass
867
    
868
    def _get_permissions(self, path):
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', [])
874
        if r:
875
            self.permissions.access_grant(path, READ, r)
876
        w = permissions.get('write', [])
877
        if w:
878
            self.permissions.access_grant(path, WRITE, w)
879
    
880
    def _get_public(self, path):
881
        return self.permissions.public_check(path)
882
    
883
    def _put_public(self, path, public):
884
        if not public:
885
            self.permissions.public_unset(path)
886
        else:
887
            self.permissions.public_set(path)
888
    
889
    def _del_sharing(self, path):
890
        self.permissions.access_revoke_all(path)
891
        self.permissions.public_unset(path)
892 845
    
893 846
    def _can_read(self, user, account, container, name):
894 847
        if user == account:
......
904 857
        if not self.permissions.access_check(path, WRITE, user):
905 858
            raise NotAllowedError
906 859
    
907
    def _allowed_paths(self, user, prefix=None):
908
        if prefix:
909
            prefix += '/'
910
        return self.permissions.access_list_paths(user, prefix)
911
    
912 860
    def _allowed_accounts(self, user):
913 861
        allow = set()
914
        for path in self._allowed_paths(user):
862
        for path in self.permissions.access_list_paths(user):
915 863
            allow.add(path.split('/', 1)[0])
916 864
        return sorted(allow)
917 865
    
918 866
    def _allowed_containers(self, user, account):
919 867
        allow = set()
920
        for path in self._allowed_paths(user, account):
868
        for path in self.permissions.access_list_paths(user, account):
921 869
            allow.add(path.split('/', 2)[1])
922 870
        return sorted(allow)
923
    
924
    def _shared_paths(self, prefix):
925
        prefix += '/'
926
        return self.permissions.access_list_shared(prefix)

Also available in: Unified diff